@buenojs/bueno 0.8.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 (120) hide show
  1. package/.env.example +109 -0
  2. package/.github/workflows/ci.yml +31 -0
  3. package/LICENSE +21 -0
  4. package/README.md +892 -0
  5. package/architecture.md +652 -0
  6. package/bun.lock +70 -0
  7. package/dist/cli/index.js +3233 -0
  8. package/dist/index.js +9014 -0
  9. package/package.json +77 -0
  10. package/src/cache/index.ts +795 -0
  11. package/src/cli/ARCHITECTURE.md +837 -0
  12. package/src/cli/bin.ts +10 -0
  13. package/src/cli/commands/build.ts +425 -0
  14. package/src/cli/commands/dev.ts +248 -0
  15. package/src/cli/commands/generate.ts +541 -0
  16. package/src/cli/commands/help.ts +55 -0
  17. package/src/cli/commands/index.ts +112 -0
  18. package/src/cli/commands/migration.ts +355 -0
  19. package/src/cli/commands/new.ts +804 -0
  20. package/src/cli/commands/start.ts +208 -0
  21. package/src/cli/core/args.ts +283 -0
  22. package/src/cli/core/console.ts +349 -0
  23. package/src/cli/core/index.ts +60 -0
  24. package/src/cli/core/prompt.ts +424 -0
  25. package/src/cli/core/spinner.ts +265 -0
  26. package/src/cli/index.ts +135 -0
  27. package/src/cli/templates/deploy.ts +295 -0
  28. package/src/cli/templates/docker.ts +307 -0
  29. package/src/cli/templates/index.ts +24 -0
  30. package/src/cli/utils/fs.ts +428 -0
  31. package/src/cli/utils/index.ts +8 -0
  32. package/src/cli/utils/strings.ts +197 -0
  33. package/src/config/env.ts +408 -0
  34. package/src/config/index.ts +506 -0
  35. package/src/config/loader.ts +329 -0
  36. package/src/config/merge.ts +285 -0
  37. package/src/config/types.ts +320 -0
  38. package/src/config/validation.ts +441 -0
  39. package/src/container/forward-ref.ts +143 -0
  40. package/src/container/index.ts +386 -0
  41. package/src/context/index.ts +360 -0
  42. package/src/database/index.ts +1142 -0
  43. package/src/database/migrations/index.ts +371 -0
  44. package/src/database/schema/index.ts +619 -0
  45. package/src/frontend/api-routes.ts +640 -0
  46. package/src/frontend/bundler.ts +643 -0
  47. package/src/frontend/console-client.ts +419 -0
  48. package/src/frontend/console-stream.ts +587 -0
  49. package/src/frontend/dev-server.ts +846 -0
  50. package/src/frontend/file-router.ts +611 -0
  51. package/src/frontend/frameworks/index.ts +106 -0
  52. package/src/frontend/frameworks/react.ts +85 -0
  53. package/src/frontend/frameworks/solid.ts +104 -0
  54. package/src/frontend/frameworks/svelte.ts +110 -0
  55. package/src/frontend/frameworks/vue.ts +92 -0
  56. package/src/frontend/hmr-client.ts +663 -0
  57. package/src/frontend/hmr.ts +728 -0
  58. package/src/frontend/index.ts +342 -0
  59. package/src/frontend/islands.ts +552 -0
  60. package/src/frontend/isr.ts +555 -0
  61. package/src/frontend/layout.ts +475 -0
  62. package/src/frontend/ssr/react.ts +446 -0
  63. package/src/frontend/ssr/solid.ts +523 -0
  64. package/src/frontend/ssr/svelte.ts +546 -0
  65. package/src/frontend/ssr/vue.ts +504 -0
  66. package/src/frontend/ssr.ts +699 -0
  67. package/src/frontend/types.ts +2274 -0
  68. package/src/health/index.ts +604 -0
  69. package/src/index.ts +410 -0
  70. package/src/lock/index.ts +587 -0
  71. package/src/logger/index.ts +444 -0
  72. package/src/logger/transports/index.ts +969 -0
  73. package/src/metrics/index.ts +494 -0
  74. package/src/middleware/built-in.ts +360 -0
  75. package/src/middleware/index.ts +94 -0
  76. package/src/modules/filters.ts +458 -0
  77. package/src/modules/guards.ts +405 -0
  78. package/src/modules/index.ts +1256 -0
  79. package/src/modules/interceptors.ts +574 -0
  80. package/src/modules/lazy.ts +418 -0
  81. package/src/modules/lifecycle.ts +478 -0
  82. package/src/modules/metadata.ts +90 -0
  83. package/src/modules/pipes.ts +626 -0
  84. package/src/router/index.ts +339 -0
  85. package/src/router/linear.ts +371 -0
  86. package/src/router/regex.ts +292 -0
  87. package/src/router/tree.ts +562 -0
  88. package/src/rpc/index.ts +1263 -0
  89. package/src/security/index.ts +436 -0
  90. package/src/ssg/index.ts +631 -0
  91. package/src/storage/index.ts +456 -0
  92. package/src/telemetry/index.ts +1097 -0
  93. package/src/testing/index.ts +1586 -0
  94. package/src/types/index.ts +236 -0
  95. package/src/types/optional-deps.d.ts +219 -0
  96. package/src/validation/index.ts +276 -0
  97. package/src/websocket/index.ts +1004 -0
  98. package/tests/integration/cli.test.ts +1016 -0
  99. package/tests/integration/fullstack.test.ts +234 -0
  100. package/tests/unit/cache.test.ts +174 -0
  101. package/tests/unit/cli-commands.test.ts +892 -0
  102. package/tests/unit/cli.test.ts +1258 -0
  103. package/tests/unit/container.test.ts +279 -0
  104. package/tests/unit/context.test.ts +221 -0
  105. package/tests/unit/database.test.ts +183 -0
  106. package/tests/unit/linear-router.test.ts +280 -0
  107. package/tests/unit/lock.test.ts +336 -0
  108. package/tests/unit/middleware.test.ts +184 -0
  109. package/tests/unit/modules.test.ts +142 -0
  110. package/tests/unit/pubsub.test.ts +257 -0
  111. package/tests/unit/regex-router.test.ts +265 -0
  112. package/tests/unit/router.test.ts +373 -0
  113. package/tests/unit/rpc.test.ts +1248 -0
  114. package/tests/unit/security.test.ts +174 -0
  115. package/tests/unit/telemetry.test.ts +371 -0
  116. package/tests/unit/test-cache.test.ts +110 -0
  117. package/tests/unit/test-database.test.ts +282 -0
  118. package/tests/unit/tree-router.test.ts +325 -0
  119. package/tests/unit/validation.test.ts +794 -0
  120. package/tsconfig.json +27 -0
@@ -0,0 +1,3233 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+ var __defProp = Object.defineProperty;
4
+ var __export = (target, all) => {
5
+ for (var name in all)
6
+ __defProp(target, name, {
7
+ get: all[name],
8
+ enumerable: true,
9
+ configurable: true,
10
+ set: (newValue) => all[name] = () => newValue
11
+ });
12
+ };
13
+ var __legacyDecorateClassTS = function(decorators, target, key, desc) {
14
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
15
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
16
+ r = Reflect.decorate(decorators, target, key, desc);
17
+ else
18
+ for (var i = decorators.length - 1;i >= 0; i--)
19
+ if (d = decorators[i])
20
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
21
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
22
+ };
23
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
24
+ var __require = import.meta.require;
25
+
26
+ // src/cli/core/args.ts
27
+ function parseArgs(argv = process.argv.slice(2)) {
28
+ const result = {
29
+ command: "",
30
+ positionals: [],
31
+ options: {},
32
+ flags: new Set
33
+ };
34
+ for (let i = 0;i < argv.length; i++) {
35
+ const arg = argv[i];
36
+ if (!arg)
37
+ continue;
38
+ if (arg.startsWith("--")) {
39
+ const eqIndex = arg.indexOf("=");
40
+ if (eqIndex !== -1) {
41
+ const name = arg.slice(2, eqIndex);
42
+ const value = arg.slice(eqIndex + 1);
43
+ result.options[name] = value;
44
+ } else {
45
+ const name = arg.slice(2);
46
+ const nextArg = argv[i + 1];
47
+ if (!nextArg || nextArg.startsWith("-")) {
48
+ result.options[name] = true;
49
+ result.flags.add(name);
50
+ } else {
51
+ result.options[name] = nextArg;
52
+ i++;
53
+ }
54
+ }
55
+ } else if (arg.startsWith("-") && arg.length > 1) {
56
+ const chars = arg.slice(1);
57
+ if (chars.length > 1) {
58
+ for (const char of chars) {
59
+ result.options[char] = true;
60
+ result.flags.add(char);
61
+ }
62
+ } else {
63
+ const name = chars;
64
+ const nextArg = argv[i + 1];
65
+ if (!nextArg || nextArg.startsWith("-")) {
66
+ result.options[name] = true;
67
+ result.flags.add(name);
68
+ } else {
69
+ result.options[name] = nextArg;
70
+ i++;
71
+ }
72
+ }
73
+ } else {
74
+ if (!result.command) {
75
+ result.command = arg;
76
+ } else {
77
+ result.positionals.push(arg);
78
+ }
79
+ }
80
+ }
81
+ return result;
82
+ }
83
+ function getOption(parsed, name, definition) {
84
+ const value = parsed.options[name] ?? parsed.options[definition.alias ?? ""];
85
+ if (value === undefined) {
86
+ return definition.default;
87
+ }
88
+ if (definition.type === "boolean") {
89
+ return value === true || value === "true";
90
+ }
91
+ if (definition.type === "number") {
92
+ return typeof value === "number" ? value : typeof value === "string" ? parseInt(value, 10) : NaN;
93
+ }
94
+ return value;
95
+ }
96
+ function hasFlag(parsed, name, alias) {
97
+ return parsed.flags.has(name) || (alias ? parsed.flags.has(alias) : false);
98
+ }
99
+ function getOptionValues(parsed, name, alias) {
100
+ const values = [];
101
+ const argv = process.argv.slice(2);
102
+ for (let i = 0;i < argv.length; i++) {
103
+ const arg = argv[i];
104
+ if (!arg)
105
+ continue;
106
+ if (arg === `--${name}`) {
107
+ const nextArg = argv[i + 1];
108
+ if (nextArg && !nextArg.startsWith("-")) {
109
+ values.push(nextArg);
110
+ i++;
111
+ }
112
+ } else if (arg.startsWith(`--${name}=`)) {
113
+ const value = arg.slice(name.length + 3);
114
+ values.push(value);
115
+ } else if (alias && arg === `-${alias}`) {
116
+ const nextArg = argv[i + 1];
117
+ if (nextArg && !nextArg.startsWith("-")) {
118
+ values.push(nextArg);
119
+ i++;
120
+ }
121
+ }
122
+ }
123
+ return values;
124
+ }
125
+ function generateHelpText(command, cliName = "bueno") {
126
+ const lines = [];
127
+ lines.push(`
128
+ ${command.description}
129
+ `);
130
+ lines.push("Usage:");
131
+ let usage = ` ${cliName} ${command.name}`;
132
+ if (command.positionals) {
133
+ for (const pos of command.positionals) {
134
+ usage += pos.required ? ` <${pos.name}>` : ` [${pos.name}]`;
135
+ }
136
+ }
137
+ usage += " [options]";
138
+ lines.push(usage + `
139
+ `);
140
+ if (command.positionals && command.positionals.length > 0) {
141
+ lines.push("Arguments:");
142
+ for (const pos of command.positionals) {
143
+ const required = pos.required ? " (required)" : "";
144
+ lines.push(` ${pos.name.padEnd(20)} ${pos.description}${required}`);
145
+ }
146
+ lines.push("");
147
+ }
148
+ if (command.options && command.options.length > 0) {
149
+ lines.push("Options:");
150
+ for (const opt of command.options) {
151
+ let flag = `--${opt.name}`;
152
+ if (opt.alias) {
153
+ flag = `-${opt.alias}, ${flag}`;
154
+ }
155
+ let defaultValue = "";
156
+ if (opt.default !== undefined) {
157
+ defaultValue = ` (default: ${opt.default})`;
158
+ }
159
+ lines.push(` ${flag.padEnd(20)} ${opt.description}${defaultValue}`);
160
+ }
161
+ lines.push("");
162
+ }
163
+ if (command.examples && command.examples.length > 0) {
164
+ lines.push("Examples:");
165
+ for (const example of command.examples) {
166
+ lines.push(` ${example}`);
167
+ }
168
+ lines.push("");
169
+ }
170
+ return lines.join(`
171
+ `);
172
+ }
173
+ function generateGlobalHelpText(commands, cliName = "bueno") {
174
+ const lines = [];
175
+ lines.push(`
176
+ ${cliName} - A Bun-Native Full-Stack Framework CLI
177
+ `);
178
+ lines.push("Usage:");
179
+ lines.push(` ${cliName} <command> [options]
180
+ `);
181
+ lines.push("Commands:");
182
+ for (const cmd of commands) {
183
+ const name = cmd.alias ? `${cmd.name} (${cmd.alias})` : cmd.name;
184
+ lines.push(` ${name.padEnd(20)} ${cmd.description}`);
185
+ }
186
+ lines.push("");
187
+ lines.push("Global Options:");
188
+ lines.push(" --help, -h Show help for command");
189
+ lines.push(" --version, -v Show CLI version");
190
+ lines.push(" --verbose Enable verbose output");
191
+ lines.push(" --quiet Suppress non-essential output");
192
+ lines.push(" --no-color Disable colored output");
193
+ lines.push("");
194
+ lines.push(`Run '${cliName} <command> --help' for more information about a command.
195
+ `);
196
+ return lines.join(`
197
+ `);
198
+ }
199
+
200
+ // src/cli/core/console.ts
201
+ var COLORS = {
202
+ reset: "\x1B[0m",
203
+ bold: "\x1B[1m",
204
+ dim: "\x1B[2m",
205
+ italic: "\x1B[3m",
206
+ underline: "\x1B[4m",
207
+ black: "\x1B[30m",
208
+ red: "\x1B[31m",
209
+ green: "\x1B[32m",
210
+ yellow: "\x1B[33m",
211
+ blue: "\x1B[34m",
212
+ magenta: "\x1B[35m",
213
+ cyan: "\x1B[36m",
214
+ white: "\x1B[37m",
215
+ brightRed: "\x1B[91m",
216
+ brightGreen: "\x1B[92m",
217
+ brightYellow: "\x1B[93m",
218
+ brightBlue: "\x1B[94m",
219
+ brightMagenta: "\x1B[95m",
220
+ brightCyan: "\x1B[96m",
221
+ brightWhite: "\x1B[97m",
222
+ bgBlack: "\x1B[40m",
223
+ bgRed: "\x1B[41m",
224
+ bgGreen: "\x1B[42m",
225
+ bgYellow: "\x1B[43m",
226
+ bgBlue: "\x1B[44m",
227
+ bgMagenta: "\x1B[45m",
228
+ bgCyan: "\x1B[46m",
229
+ bgWhite: "\x1B[47m"
230
+ };
231
+ var colorEnabled = !process.env.NO_COLOR && process.env.BUENO_NO_COLOR !== "true" && process.stdout.isTTY;
232
+ function setColorEnabled(enabled) {
233
+ colorEnabled = enabled;
234
+ }
235
+ function isColorEnabled() {
236
+ return colorEnabled;
237
+ }
238
+ function colorize(text, color) {
239
+ if (!colorEnabled)
240
+ return text;
241
+ return `${COLORS[color]}${text}${COLORS.reset}`;
242
+ }
243
+ var colors = {
244
+ red: (text) => colorize(text, "red"),
245
+ green: (text) => colorize(text, "green"),
246
+ yellow: (text) => colorize(text, "yellow"),
247
+ blue: (text) => colorize(text, "blue"),
248
+ magenta: (text) => colorize(text, "magenta"),
249
+ cyan: (text) => colorize(text, "cyan"),
250
+ white: (text) => colorize(text, "white"),
251
+ brightRed: (text) => colorize(text, "brightRed"),
252
+ brightGreen: (text) => colorize(text, "brightGreen"),
253
+ brightYellow: (text) => colorize(text, "brightYellow"),
254
+ brightBlue: (text) => colorize(text, "brightBlue"),
255
+ brightCyan: (text) => colorize(text, "brightCyan"),
256
+ dim: (text) => colorize(text, "dim"),
257
+ bold: (text) => colorize(text, "bold"),
258
+ underline: (text) => colorize(text, "underline"),
259
+ italic: (text) => colorize(text, "italic")
260
+ };
261
+ var cliConsole = {
262
+ log(message, ...args) {
263
+ globalThis.console.log(message, ...args);
264
+ },
265
+ info(message, ...args) {
266
+ globalThis.console.log(colors.cyan("\u2139"), message, ...args);
267
+ },
268
+ success(message, ...args) {
269
+ globalThis.console.log(colors.green("\u2713"), message, ...args);
270
+ },
271
+ warn(message, ...args) {
272
+ globalThis.console.log(colors.yellow("\u26A0"), message, ...args);
273
+ },
274
+ error(message, ...args) {
275
+ globalThis.console.error(colors.red("\u2717"), message, ...args);
276
+ },
277
+ debug(message, ...args) {
278
+ if (process.env.BUENO_VERBOSE === "true") {
279
+ globalThis.console.log(colors.dim("\u22EF"), colors.dim(message), ...args);
280
+ }
281
+ },
282
+ header(title) {
283
+ globalThis.console.log();
284
+ globalThis.console.log(colors.bold(colors.cyan(title)));
285
+ globalThis.console.log();
286
+ },
287
+ subheader(title) {
288
+ globalThis.console.log();
289
+ globalThis.console.log(colors.bold(title));
290
+ },
291
+ newline() {
292
+ globalThis.console.log();
293
+ },
294
+ clear() {
295
+ process.stdout.write("\x1B[2J\x1B[0f");
296
+ }
297
+ };
298
+ function formatTable(headers, rows, options = {}) {
299
+ const padding = options.padding ?? 2;
300
+ const widths = headers.map((h, i) => {
301
+ const maxRowWidth = Math.max(...rows.map((r) => r[i]?.length ?? 0));
302
+ return Math.max(h.length, maxRowWidth);
303
+ });
304
+ const pad = " ".repeat(padding);
305
+ const headerLine = headers.map((h, i) => h.padEnd(widths[i] ?? 0)).join(pad);
306
+ const separator = widths.map((w) => "\u2500".repeat(w)).join(pad);
307
+ const rowLines = rows.map((row) => row.map((cell, i) => (cell ?? "").padEnd(widths[i] ?? 0)).join(pad));
308
+ return [
309
+ colors.bold(headerLine),
310
+ colors.dim(separator),
311
+ ...rowLines
312
+ ].join(`
313
+ `);
314
+ }
315
+ function printTable(headers, rows, options) {
316
+ globalThis.console.log(formatTable(headers, rows, options));
317
+ }
318
+ function formatSize(bytes) {
319
+ const units = ["B", "KB", "MB", "GB"];
320
+ let size = bytes;
321
+ let unitIndex = 0;
322
+ while (size >= 1024 && unitIndex < units.length - 1) {
323
+ size /= 1024;
324
+ unitIndex++;
325
+ }
326
+ return `${size.toFixed(1)} ${units[unitIndex]}`;
327
+ }
328
+ function formatDuration(ms) {
329
+ if (ms < 1000)
330
+ return `${ms}ms`;
331
+ if (ms < 60000)
332
+ return `${(ms / 1000).toFixed(1)}s`;
333
+ return `${(ms / 60000).toFixed(1)}m`;
334
+ }
335
+
336
+ // src/cli/commands/index.ts
337
+ class CommandRegistry {
338
+ commands = new Map;
339
+ aliases = new Map;
340
+ register(definition, handler) {
341
+ this.commands.set(definition.name, {
342
+ definition,
343
+ handler
344
+ });
345
+ if (definition.alias) {
346
+ this.aliases.set(definition.alias, definition.name);
347
+ }
348
+ }
349
+ get(name) {
350
+ const commandName = this.aliases.get(name) ?? name;
351
+ return this.commands.get(commandName);
352
+ }
353
+ has(name) {
354
+ const commandName = this.aliases.get(name) ?? name;
355
+ return this.commands.has(commandName);
356
+ }
357
+ getAll() {
358
+ return Array.from(this.commands.values()).map((c) => c.definition);
359
+ }
360
+ getCommands() {
361
+ return new Map(this.commands);
362
+ }
363
+ async execute(name, args) {
364
+ const command = this.get(name);
365
+ if (!command) {
366
+ throw new Error(`Unknown command: ${name}`);
367
+ }
368
+ await command.handler(args);
369
+ }
370
+ }
371
+ var registry = new CommandRegistry;
372
+ function defineCommand(definition, handler) {
373
+ registry.register(definition, handler);
374
+ }
375
+
376
+ // src/cli/core/prompt.ts
377
+ import * as readline from "readline";
378
+ function isInteractive() {
379
+ return !!(process.stdin.isTTY && process.stdout.isTTY);
380
+ }
381
+ function createRL() {
382
+ return readline.createInterface({
383
+ input: process.stdin,
384
+ output: process.stdout
385
+ });
386
+ }
387
+ async function prompt(message, options = {}) {
388
+ const defaultValue = options.default;
389
+ const promptText = defaultValue ? `${colors.cyan("?")} ${message} ${colors.dim(`(${defaultValue})`)}: ` : `${colors.cyan("?")} ${message}: `;
390
+ if (!isInteractive()) {
391
+ return defaultValue ?? "";
392
+ }
393
+ return new Promise((resolve) => {
394
+ const rl = createRL();
395
+ rl.question(promptText, (answer) => {
396
+ rl.close();
397
+ const value = answer.trim() || defaultValue || "";
398
+ if (options.validate) {
399
+ const result = options.validate(value);
400
+ if (result !== true) {
401
+ const errorMsg = typeof result === "string" ? result : "Invalid value";
402
+ process.stdout.write(`${colors.red("\u2717")} ${errorMsg}
403
+ `);
404
+ prompt(message, options).then(resolve);
405
+ return;
406
+ }
407
+ }
408
+ resolve(value);
409
+ });
410
+ });
411
+ }
412
+ async function confirm(message, options = {}) {
413
+ const defaultValue = options.default ?? false;
414
+ const hint = defaultValue ? "Y/n" : "y/N";
415
+ if (!isInteractive()) {
416
+ return defaultValue;
417
+ }
418
+ const answer = await prompt(`${message} ${colors.dim(`(${hint})`)}`, {
419
+ default: defaultValue ? "y" : "n",
420
+ validate: (value) => {
421
+ if (!value)
422
+ return true;
423
+ return ["y", "yes", "n", "no"].includes(value.toLowerCase()) || "Please enter y or n";
424
+ }
425
+ });
426
+ return ["y", "yes"].includes(answer.toLowerCase());
427
+ }
428
+ async function select(message, choices, options = {}) {
429
+ if (!isInteractive()) {
430
+ return options.default ?? choices[0]?.value;
431
+ }
432
+ const pageSize = options.pageSize ?? 10;
433
+ let selectedIndex = choices.findIndex((c) => c.value === options.default && !c.disabled);
434
+ if (selectedIndex === -1) {
435
+ selectedIndex = choices.findIndex((c) => !c.disabled);
436
+ }
437
+ return new Promise((resolve) => {
438
+ process.stdout.write("\x1B[?25l");
439
+ const render = () => {
440
+ const lines = Math.min(choices.length, pageSize);
441
+ process.stdout.write(`\x1B[${lines + 1}A\x1B[0J`);
442
+ process.stdout.write(`${colors.cyan("?")} ${message}
443
+ `);
444
+ const start = Math.max(0, selectedIndex - pageSize + 1);
445
+ const end = Math.min(choices.length, start + pageSize);
446
+ for (let i = start;i < end; i++) {
447
+ const choice = choices[i];
448
+ if (!choice)
449
+ continue;
450
+ const isSelected = i === selectedIndex;
451
+ const prefix = isSelected ? `${colors.cyan("\u276F")} ` : " ";
452
+ const name = choice.name ?? choice.value;
453
+ const text = choice.disabled ? colors.dim(`${name} (disabled)`) : isSelected ? colors.cyan(name) : name;
454
+ process.stdout.write(`${prefix}${text}
455
+ `);
456
+ }
457
+ };
458
+ process.stdout.write(`${colors.cyan("?")} ${message}
459
+ `);
460
+ render();
461
+ const stdin = process.stdin;
462
+ stdin.setRawMode(true);
463
+ stdin.resume();
464
+ stdin.setEncoding("utf8");
465
+ const cleanup = () => {
466
+ stdin.setRawMode(false);
467
+ stdin.pause();
468
+ stdin.removeListener("data", handler);
469
+ process.stdout.write("\x1B[?25h");
470
+ };
471
+ const handler = (key) => {
472
+ if (key === "\x1B[A" || key === "k") {
473
+ do {
474
+ selectedIndex = (selectedIndex - 1 + choices.length) % choices.length;
475
+ } while (choices[selectedIndex]?.disabled);
476
+ render();
477
+ } else if (key === "\x1B[B" || key === "j") {
478
+ do {
479
+ selectedIndex = (selectedIndex + 1) % choices.length;
480
+ } while (choices[selectedIndex]?.disabled);
481
+ render();
482
+ } else if (key === "\r" || key === `
483
+ `) {
484
+ cleanup();
485
+ const selected = choices[selectedIndex];
486
+ if (selected) {
487
+ process.stdout.write(`\x1B[${Math.min(choices.length, pageSize) + 1}A\x1B[0J`);
488
+ process.stdout.write(`${colors.cyan("?")} ${message} ${colors.cyan(selected.name ?? selected.value)}
489
+ `);
490
+ resolve(selected.value);
491
+ }
492
+ } else if (key === "\x1B" || key === "\x03") {
493
+ cleanup();
494
+ process.stdout.write(`
495
+ `);
496
+ process.exit(130);
497
+ }
498
+ };
499
+ stdin.on("data", handler);
500
+ });
501
+ }
502
+
503
+ // src/cli/core/spinner.ts
504
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
505
+ var SPINNER_INTERVAL = 80;
506
+
507
+ class Spinner {
508
+ text;
509
+ color;
510
+ frameIndex = 0;
511
+ interval = null;
512
+ isSpinning = false;
513
+ stream = process.stdout;
514
+ constructor(options = {}) {
515
+ this.text = options.text ?? "";
516
+ this.color = options.color ?? "cyan";
517
+ }
518
+ start(text) {
519
+ if (text)
520
+ this.text = text;
521
+ if (this.isSpinning)
522
+ return this;
523
+ this.isSpinning = true;
524
+ this.frameIndex = 0;
525
+ this.stream.write("\x1B[?25l");
526
+ this.interval = setInterval(() => {
527
+ this.render();
528
+ }, SPINNER_INTERVAL);
529
+ return this;
530
+ }
531
+ update(text) {
532
+ this.text = text;
533
+ if (this.isSpinning) {
534
+ this.render();
535
+ }
536
+ return this;
537
+ }
538
+ success(text) {
539
+ return this.stop(colors.green("\u2713"), text);
540
+ }
541
+ error(text) {
542
+ return this.stop(colors.red("\u2717"), text);
543
+ }
544
+ warn(text) {
545
+ return this.stop(colors.yellow("\u26A0"), text);
546
+ }
547
+ info(text) {
548
+ return this.stop(colors.cyan("\u2139"), text);
549
+ }
550
+ stop(symbol, text) {
551
+ if (!this.isSpinning)
552
+ return this;
553
+ this.isSpinning = false;
554
+ if (this.interval) {
555
+ clearInterval(this.interval);
556
+ this.interval = null;
557
+ }
558
+ this.stream.write("\r\x1B[K");
559
+ const finalText = text ?? this.text;
560
+ if (symbol) {
561
+ this.stream.write(`${symbol} ${finalText}
562
+ `);
563
+ } else {
564
+ this.stream.write(`${finalText}
565
+ `);
566
+ }
567
+ this.stream.write("\x1B[?25h");
568
+ return this;
569
+ }
570
+ clear() {
571
+ if (!this.isSpinning)
572
+ return this;
573
+ this.stream.write("\r\x1B[K");
574
+ return this;
575
+ }
576
+ render() {
577
+ if (!isColorEnabled()) {
578
+ const dots = ".".repeat(this.frameIndex % 3 + 1);
579
+ this.stream.write(`\r${this.text}${dots} `);
580
+ this.frameIndex++;
581
+ return;
582
+ }
583
+ const frame = SPINNER_FRAMES[this.frameIndex % SPINNER_FRAMES.length];
584
+ const coloredFrame = colors[this.color](frame);
585
+ this.stream.write(`\r${coloredFrame} ${this.text}`);
586
+ this.frameIndex++;
587
+ }
588
+ }
589
+ function spinner(text, options) {
590
+ return new Spinner({ text, ...options }).start();
591
+ }
592
+
593
+ class ProgressBar {
594
+ total;
595
+ width;
596
+ text;
597
+ completeChar;
598
+ incompleteChar;
599
+ current = 0;
600
+ stream = process.stdout;
601
+ constructor(options) {
602
+ this.total = options.total;
603
+ this.width = options.width ?? 40;
604
+ this.text = options.text ?? "";
605
+ this.completeChar = options.completeChar ?? "\u2588";
606
+ this.incompleteChar = options.incompleteChar ?? "\u2591";
607
+ }
608
+ start() {
609
+ this.current = 0;
610
+ this.render();
611
+ return this;
612
+ }
613
+ update(current) {
614
+ this.current = Math.min(current, this.total);
615
+ this.render();
616
+ return this;
617
+ }
618
+ increment(amount = 1) {
619
+ return this.update(this.current + amount);
620
+ }
621
+ complete() {
622
+ this.current = this.total;
623
+ this.render();
624
+ this.stream.write(`
625
+ `);
626
+ return this;
627
+ }
628
+ render() {
629
+ const percent = this.current / this.total;
630
+ const completeWidth = Math.round(this.width * percent);
631
+ const incompleteWidth = this.width - completeWidth;
632
+ const complete = this.completeChar.repeat(completeWidth);
633
+ const incomplete = this.incompleteChar.repeat(incompleteWidth);
634
+ const bar = colors.green(complete) + colors.dim(incomplete);
635
+ const percentText = `${Math.round(percent * 100)}%`.padStart(4);
636
+ const line = `\r${this.text} [${bar}] ${percentText} ${this.current}/${this.total}`;
637
+ this.stream.write(`\r\x1B[K${line}`);
638
+ }
639
+ }
640
+ async function runTasks(tasks) {
641
+ for (const task of tasks) {
642
+ const s = spinner(task.text);
643
+ try {
644
+ await task.task();
645
+ s.success();
646
+ } catch (error) {
647
+ s.error();
648
+ throw error;
649
+ }
650
+ }
651
+ }
652
+
653
+ // src/cli/utils/fs.ts
654
+ import * as fs from "fs";
655
+ import * as path from "path";
656
+ async function fileExists(filePath) {
657
+ try {
658
+ return await Bun.file(filePath).exists();
659
+ } catch {
660
+ return false;
661
+ }
662
+ }
663
+ async function createDirectory(dirPath) {
664
+ await fs.promises.mkdir(dirPath, { recursive: true });
665
+ }
666
+ async function readFile(filePath) {
667
+ return await Bun.file(filePath).text();
668
+ }
669
+ async function writeFile(filePath, content) {
670
+ const dir = path.dirname(filePath);
671
+ await createDirectory(dir);
672
+ await Bun.write(filePath, content);
673
+ }
674
+ async function deleteDirectory(dirPath) {
675
+ await fs.promises.rm(dirPath, { recursive: true, force: true });
676
+ }
677
+ async function listFiles(dirPath, options = {}) {
678
+ const files = [];
679
+ async function walk(dir) {
680
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
681
+ for (const entry of entries) {
682
+ const fullPath = path.join(dir, entry.name);
683
+ if (entry.isDirectory() && options.recursive) {
684
+ await walk(fullPath);
685
+ } else if (entry.isFile()) {
686
+ if (!options.pattern || options.pattern.test(entry.name)) {
687
+ files.push(fullPath);
688
+ }
689
+ }
690
+ }
691
+ }
692
+ await walk(dirPath);
693
+ return files;
694
+ }
695
+ async function findFileUp(startDir, fileName, options = {}) {
696
+ let currentDir = startDir;
697
+ const stopAt = options.stopAt ?? "/";
698
+ while (currentDir !== stopAt && currentDir !== "/") {
699
+ const filePath = path.join(currentDir, fileName);
700
+ if (await fileExists(filePath)) {
701
+ return filePath;
702
+ }
703
+ currentDir = path.dirname(currentDir);
704
+ }
705
+ return null;
706
+ }
707
+ async function getProjectRoot(startDir = process.cwd()) {
708
+ const packageJsonPath = await findFileUp(startDir, "package.json");
709
+ if (packageJsonPath) {
710
+ return path.dirname(packageJsonPath);
711
+ }
712
+ return null;
713
+ }
714
+ async function isBuenoProject(dir = process.cwd()) {
715
+ const root = await getProjectRoot(dir);
716
+ if (!root)
717
+ return false;
718
+ const configPath = path.join(root, "bueno.config.ts");
719
+ if (await fileExists(configPath))
720
+ return true;
721
+ const packageJsonPath = path.join(root, "package.json");
722
+ if (await fileExists(packageJsonPath)) {
723
+ const content = await readFile(packageJsonPath);
724
+ try {
725
+ const pkg = JSON.parse(content);
726
+ return !!(pkg.dependencies?.bueno || pkg.devDependencies?.bueno);
727
+ } catch {
728
+ return false;
729
+ }
730
+ }
731
+ return false;
732
+ }
733
+ function joinPaths(...paths) {
734
+ return path.join(...paths);
735
+ }
736
+ function processTemplate(template, data) {
737
+ let result = template;
738
+ result = result.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (_, key, content) => {
739
+ const value = data[key];
740
+ return value ? content : "";
741
+ });
742
+ result = result.replace(/\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g, (_, key, content) => {
743
+ const items = data[key];
744
+ if (!Array.isArray(items))
745
+ return "";
746
+ return items.map((item) => {
747
+ let itemContent = content;
748
+ if (typeof item === "object" && item !== null) {
749
+ for (const [k, v] of Object.entries(item)) {
750
+ itemContent = itemContent.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), String(v));
751
+ }
752
+ }
753
+ return itemContent;
754
+ }).join("");
755
+ });
756
+ const helpers = {
757
+ camelCase: (v) => v.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toLowerCase()),
758
+ pascalCase: (v) => v.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toUpperCase()),
759
+ kebabCase: (v) => v.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[-_\s]+/g, "-").toLowerCase(),
760
+ snakeCase: (v) => v.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase(),
761
+ upperCase: (v) => v.toUpperCase(),
762
+ lowerCase: (v) => v.toLowerCase(),
763
+ capitalize: (v) => v.charAt(0).toUpperCase() + v.slice(1),
764
+ pluralize: (v) => {
765
+ if (v.endsWith("y") && !["ay", "ey", "iy", "oy", "uy"].some((e) => v.endsWith(e))) {
766
+ return v.slice(0, -1) + "ies";
767
+ }
768
+ if (v.endsWith("s") || v.endsWith("x") || v.endsWith("z") || v.endsWith("ch") || v.endsWith("sh")) {
769
+ return v + "es";
770
+ }
771
+ return v + "s";
772
+ }
773
+ };
774
+ for (const [helperName, helperFn] of Object.entries(helpers)) {
775
+ const regex = new RegExp(`\\{\\{${helperName}\\s+(\\w+)\\}\\}`, "g");
776
+ result = result.replace(regex, (_, key) => {
777
+ const value = data[key];
778
+ if (typeof value === "string") {
779
+ return helperFn(value);
780
+ }
781
+ return String(value);
782
+ });
783
+ }
784
+ for (const [key, value] of Object.entries(data)) {
785
+ const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
786
+ result = result.replace(regex, String(value));
787
+ }
788
+ result = result.replace(/^\s*\n/gm, `
789
+ `);
790
+ result = result.replace(/\n{3,}/g, `
791
+
792
+ `);
793
+ return result.trim();
794
+ }
795
+
796
+ // src/cli/utils/strings.ts
797
+ function kebabCase(str) {
798
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[-_\s]+/g, "-").toLowerCase();
799
+ }
800
+
801
+ // src/cli/templates/docker.ts
802
+ function getDockerfileTemplate(projectName, database) {
803
+ return `# ${projectName} - Production Dockerfile
804
+ # Multi-stage build for optimized production image
805
+
806
+ # Stage 1: Install dependencies
807
+ FROM oven/bun:1 AS deps
808
+
809
+ WORKDIR /app
810
+
811
+ # Copy package files first for better layer caching
812
+ COPY package.json bun.lock* ./
813
+
814
+ # Install dependencies
815
+ RUN bun install --frozen-lockfile --production
816
+
817
+ # Stage 2: Build the application
818
+ FROM oven/bun:1 AS builder
819
+
820
+ WORKDIR /app
821
+
822
+ # Copy package files
823
+ COPY package.json bun.lock* ./
824
+
825
+ # Install all dependencies (including devDependencies for build)
826
+ RUN bun install --frozen-lockfile
827
+
828
+ # Copy source code
829
+ COPY . .
830
+
831
+ # Build the application
832
+ RUN bun run build
833
+
834
+ # Stage 3: Production image
835
+ FROM oven/bun:1 AS runner
836
+
837
+ WORKDIR /app
838
+
839
+ # Set production environment
840
+ ENV NODE_ENV=production
841
+ ENV BUN_ENV=production
842
+
843
+ # Create non-root user for security
844
+ RUN addgroup --system --gid 1001 bunjs \\
845
+ && adduser --system --uid 1001 --ingroup bunjs bunuser
846
+
847
+ # Copy built application from builder
848
+ COPY --from=builder /app/dist ./dist
849
+ COPY --from=builder /app/node_modules ./node_modules
850
+ COPY --from=builder /app/package.json ./
851
+
852
+ # Copy config files if they exist
853
+ COPY --from=builder /app/bueno.config.ts* ./
854
+
855
+ # Set proper ownership
856
+ RUN chown -R bunuser:bunjs /app
857
+
858
+ # Switch to non-root user
859
+ USER bunuser
860
+
861
+ # Expose the application port
862
+ EXPOSE 3000
863
+
864
+ # Health check
865
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\
866
+ CMD curl -f http://localhost:3000/health || exit 1
867
+
868
+ # Start the application
869
+ CMD ["bun", "run", "dist/main.js"]
870
+ `;
871
+ }
872
+ function getDockerignoreTemplate() {
873
+ return `# Dependencies
874
+ node_modules/
875
+
876
+ # Build output
877
+ dist/
878
+
879
+ # Environment files
880
+ .env
881
+ .env.local
882
+ .env.*.local
883
+
884
+ # IDE
885
+ .idea/
886
+ .vscode/
887
+ *.swp
888
+ *.swo
889
+
890
+ # OS
891
+ .DS_Store
892
+ Thumbs.db
893
+
894
+ # Git
895
+ .git/
896
+ .gitignore
897
+
898
+ # Docker
899
+ Dockerfile
900
+ docker-compose*.yml
901
+ .dockerignore
902
+
903
+ # Test files
904
+ tests/
905
+ coverage/
906
+ *.test.ts
907
+ *.spec.ts
908
+
909
+ # Documentation
910
+ *.md
911
+ !README.md
912
+
913
+ # Database files (local)
914
+ *.db
915
+ *.sqlite
916
+ *.sqlite3
917
+
918
+ # Logs
919
+ *.log
920
+ logs/
921
+
922
+ # Misc
923
+ .editorconfig
924
+ .eslintrc*
925
+ .prettierrc*
926
+ tsconfig.json
927
+ `;
928
+ }
929
+ function getDockerComposeTemplate(projectName, database) {
930
+ const kebabName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
931
+ let databaseServices = "";
932
+ let dependsOn = "";
933
+ if (database === "postgresql") {
934
+ databaseServices = `
935
+ # PostgreSQL Database
936
+ postgres:
937
+ image: postgres:16-alpine
938
+ container_name: ${kebabName}-postgres
939
+ restart: unless-stopped
940
+ environment:
941
+ POSTGRES_USER: \${POSTGRES_USER:-postgres}
942
+ POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-postgres}
943
+ POSTGRES_DB: \${POSTGRES_DB:-${kebabName}}
944
+ volumes:
945
+ - postgres_data:/var/lib/postgresql/data
946
+ ports:
947
+ - "\${POSTGRES_PORT:-5432}:5432"
948
+ healthcheck:
949
+ test: ["CMD-SHELL", "pg_isready -U \${POSTGRES_USER:-postgres} -d \${POSTGRES_DB:-${kebabName}}"]
950
+ interval: 10s
951
+ timeout: 5s
952
+ retries: 5
953
+ networks:
954
+ - bueno-network
955
+
956
+ `;
957
+ dependsOn = `
958
+ depends_on:
959
+ postgres:
960
+ condition: service_healthy
961
+ `;
962
+ } else if (database === "mysql") {
963
+ databaseServices = `
964
+ # MySQL Database
965
+ mysql:
966
+ image: mysql:8.0
967
+ container_name: ${kebabName}-mysql
968
+ restart: unless-stopped
969
+ environment:
970
+ MYSQL_ROOT_PASSWORD: \${MYSQL_ROOT_PASSWORD:-root}
971
+ MYSQL_USER: \${MYSQL_USER:-mysql}
972
+ MYSQL_PASSWORD: \${MYSQL_PASSWORD:-mysql}
973
+ MYSQL_DATABASE: \${MYSQL_DATABASE:-${kebabName}}
974
+ volumes:
975
+ - mysql_data:/var/lib/mysql
976
+ ports:
977
+ - "\${MYSQL_PORT:-3306}:3306"
978
+ healthcheck:
979
+ test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p\${MYSQL_ROOT_PASSWORD:-root}"]
980
+ interval: 10s
981
+ timeout: 5s
982
+ retries: 5
983
+ networks:
984
+ - bueno-network
985
+
986
+ `;
987
+ dependsOn = `
988
+ depends_on:
989
+ mysql:
990
+ condition: service_healthy
991
+ `;
992
+ }
993
+ const volumes = database === "postgresql" ? `
994
+ volumes:
995
+ postgres_data:
996
+ driver: local
997
+ ` : database === "mysql" ? `
998
+ volumes:
999
+ mysql_data:
1000
+ driver: local
1001
+ ` : "";
1002
+ const databaseEnv = database === "postgresql" ? ` DATABASE_URL: postgresql://\${POSTGRES_USER:-postgres}:\${POSTGRES_PASSWORD:-postgres}@postgres:5432/\${POSTGRES_DB:-${kebabName}}
1003
+ ` : database === "mysql" ? ` DATABASE_URL: mysql://\${MYSQL_USER:-mysql}:\${MYSQL_PASSWORD:-mysql}@mysql:3306/\${MYSQL_DATABASE:-${kebabName}}
1004
+ ` : "";
1005
+ return `# ${projectName} - Docker Compose for Local Development
1006
+ # Usage: docker-compose up -d
1007
+
1008
+ services:
1009
+ # Application Service
1010
+ app:
1011
+ build:
1012
+ context: .
1013
+ dockerfile: Dockerfile
1014
+ container_name: ${kebabName}-app
1015
+ restart: unless-stopped
1016
+ ports:
1017
+ - "\${APP_PORT:-3000}:3000"
1018
+ environment:
1019
+ NODE_ENV: production
1020
+ BUN_ENV: production
1021
+ ${databaseEnv}${dependsOn} networks:
1022
+ - bueno-network
1023
+ healthcheck:
1024
+ test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
1025
+ interval: 30s
1026
+ timeout: 10s
1027
+ retries: 3
1028
+ start_period: 10s
1029
+ ${databaseServices}networks:
1030
+ bueno-network:
1031
+ driver: bridge
1032
+ ${volumes}
1033
+ `;
1034
+ }
1035
+ function getDockerEnvTemplate(projectName, database) {
1036
+ const kebabName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
1037
+ let dbEnv = "";
1038
+ if (database === "postgresql") {
1039
+ dbEnv = `
1040
+ # PostgreSQL Configuration
1041
+ POSTGRES_USER=postgres
1042
+ POSTGRES_PASSWORD=postgres
1043
+ POSTGRES_DB=${kebabName}
1044
+ POSTGRES_PORT=5432
1045
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5432/${kebabName}
1046
+ `;
1047
+ } else if (database === "mysql") {
1048
+ dbEnv = `
1049
+ # MySQL Configuration
1050
+ MYSQL_ROOT_PASSWORD=root
1051
+ MYSQL_USER=mysql
1052
+ MYSQL_PASSWORD=mysql
1053
+ MYSQL_DATABASE=${kebabName}
1054
+ MYSQL_PORT=3306
1055
+ DATABASE_URL=mysql://mysql:mysql@localhost:3306/${kebabName}
1056
+ `;
1057
+ }
1058
+ return `# ${projectName} - Docker Environment Variables
1059
+ # Copy this file to .env and update values as needed
1060
+
1061
+ # Application
1062
+ APP_PORT=3000
1063
+ NODE_ENV=production
1064
+ ${dbEnv}`;
1065
+ }
1066
+
1067
+ // src/cli/templates/deploy.ts
1068
+ function getRenderYamlTemplate(projectName, database) {
1069
+ const kebabName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
1070
+ let databaseSection = "";
1071
+ let envVars = "";
1072
+ if (database === "postgresql") {
1073
+ databaseSection = `
1074
+ # PostgreSQL Database
1075
+ - type: pserv
1076
+ name: ${kebabName}-db
1077
+ env: docker
1078
+ region: oregon
1079
+ plan: starter
1080
+ envVars:
1081
+ - key: POSTGRES_USER
1082
+ generateValue: true
1083
+ - key: POSTGRES_PASSWORD
1084
+ generateValue: true
1085
+ - key: POSTGRES_DB
1086
+ value: ${kebabName}
1087
+ disk:
1088
+ name: postgres-data
1089
+ mountPath: /var/lib/postgresql/data
1090
+ sizeGB: 10
1091
+
1092
+ `;
1093
+ envVars = `
1094
+ envVars:
1095
+ - key: DATABASE_URL
1096
+ fromDatabase:
1097
+ name: ${kebabName}-db
1098
+ property: connectionString
1099
+ - key: NODE_ENV
1100
+ value: production
1101
+ - key: BUN_ENV
1102
+ value: production
1103
+ `;
1104
+ } else if (database === "mysql") {
1105
+ databaseSection = `
1106
+ # MySQL Database (using Render's managed MySQL)
1107
+ - type: pserv
1108
+ name: ${kebabName}-db
1109
+ env: docker
1110
+ region: oregon
1111
+ plan: starter
1112
+ envVars:
1113
+ - key: MYSQL_ROOT_PASSWORD
1114
+ generateValue: true
1115
+ - key: MYSQL_USER
1116
+ generateValue: true
1117
+ - key: MYSQL_PASSWORD
1118
+ generateValue: true
1119
+ - key: MYSQL_DATABASE
1120
+ value: ${kebabName}
1121
+ disk:
1122
+ name: mysql-data
1123
+ mountPath: /var/lib/mysql
1124
+ sizeGB: 10
1125
+
1126
+ `;
1127
+ envVars = `
1128
+ envVars:
1129
+ - key: DATABASE_URL
1130
+ fromDatabase:
1131
+ name: ${kebabName}-db
1132
+ property: connectionString
1133
+ - key: NODE_ENV
1134
+ value: production
1135
+ - key: BUN_ENV
1136
+ value: production
1137
+ `;
1138
+ } else {
1139
+ envVars = `
1140
+ envVars:
1141
+ - key: NODE_ENV
1142
+ value: production
1143
+ - key: BUN_ENV
1144
+ value: production
1145
+ `;
1146
+ }
1147
+ return `# ${projectName} - Render.com Deployment Configuration
1148
+ # https://render.com/docs/blueprint-spec
1149
+
1150
+ services:
1151
+ # Web Service
1152
+ - type: web
1153
+ name: ${kebabName}
1154
+ env: docker
1155
+ region: oregon
1156
+ plan: starter
1157
+ branch: main
1158
+ dockerfilePath: ./Dockerfile
1159
+ # dockerContext: .
1160
+ numInstances: 1
1161
+ healthCheckPath: /health
1162
+ ${envVars} # Auto-deploy on push to main branch
1163
+ autoDeploy: true
1164
+ ${databaseSection}
1165
+ # Blueprint metadata
1166
+ metadata:
1167
+ name: ${projectName}
1168
+ description: A Bueno application deployed on Render
1169
+ `;
1170
+ }
1171
+ function getFlyTomlTemplate(projectName) {
1172
+ const kebabName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
1173
+ return `# ${projectName} - Fly.io Deployment Configuration
1174
+ # https://fly.io/docs/reference/configuration/
1175
+
1176
+ app = "${kebabName}"
1177
+ primary_region = "sea"
1178
+
1179
+ [build]
1180
+ dockerfile = "Dockerfile"
1181
+
1182
+ [env]
1183
+ NODE_ENV = "production"
1184
+ BUN_ENV = "production"
1185
+ PORT = "3000"
1186
+
1187
+ [http_service]
1188
+ internal_port = 3000
1189
+ force_https = true
1190
+ auto_stop_machines = "stop"
1191
+ auto_start_machines = true
1192
+ min_machines_running = 0
1193
+ processes = ["app"]
1194
+
1195
+ [http_service.concurrency]
1196
+ type = "connections"
1197
+ hard_limit = 100
1198
+ soft_limit = 80
1199
+
1200
+ [[http_service.checks]]
1201
+ grace_period = "10s"
1202
+ interval = "30s"
1203
+ method = "GET"
1204
+ timeout = "5s"
1205
+ path = "/health"
1206
+
1207
+ [http_service.checks.headers]
1208
+ Content-Type = "application/json"
1209
+
1210
+ [[vm]]
1211
+ cpu_kind = "shared"
1212
+ cpus = 1
1213
+ memory_mb = 512
1214
+
1215
+ [[mounts]]
1216
+ source = "data"
1217
+ destination = "/app/data"
1218
+ initial_size = "1GB"
1219
+
1220
+ # Scale configuration
1221
+ # Use: fly scale count 2 # Scale to 2 machines
1222
+ # Use: fly scale vm shared-cpu-2x --memory 1024 # Upgrade VM
1223
+
1224
+ # Secrets (set via: fly secrets set KEY=VALUE)
1225
+ # DATABASE_URL=your-database-url
1226
+ # Any other sensitive environment variables
1227
+ `;
1228
+ }
1229
+ function getRailwayTomlTemplate(projectName) {
1230
+ const kebabName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
1231
+ return `# ${projectName} - Railway Deployment Configuration
1232
+ # https://docs.railway.app/reference/config-as-code
1233
+
1234
+ [build]
1235
+ builder = "DOCKERFILE"
1236
+ dockerfilePath = "Dockerfile"
1237
+
1238
+ [deploy]
1239
+ startCommand = "bun run dist/main.js"
1240
+ healthcheckPath = "/health"
1241
+ healthcheckTimeout = 300
1242
+ restartPolicyType = "ON_FAILURE"
1243
+ restartPolicyMaxRetries = 3
1244
+
1245
+ # Environment variables
1246
+ # Set these in Railway dashboard or via CLI:
1247
+ # railway variables set NODE_ENV=production
1248
+ # railway variables set DATABASE_URL=your-database-url
1249
+
1250
+ [[services]]
1251
+ name = "${kebabName}"
1252
+
1253
+ [services.variables]
1254
+ NODE_ENV = "production"
1255
+ BUN_ENV = "production"
1256
+ PORT = "3000"
1257
+
1258
+ # Health check configuration
1259
+ [[services.healthchecks]]
1260
+ path = "/health"
1261
+ interval = 30
1262
+ timeout = 10
1263
+ threshold = 3
1264
+
1265
+ # Resource configuration
1266
+ # Adjust in Railway dashboard or via CLI:
1267
+ # railway up --memory 512 --cpu 0.5
1268
+
1269
+ # Scaling configuration
1270
+ # Use Railway's autoscaling in dashboard:
1271
+ # Min instances: 0 (scale to zero)
1272
+ # Max instances: 3
1273
+ # Target CPU: 70%
1274
+ # Target Memory: 80%
1275
+ `;
1276
+ }
1277
+ function getDeployTemplate(platform, projectName, database) {
1278
+ switch (platform) {
1279
+ case "render":
1280
+ return getRenderYamlTemplate(projectName, database);
1281
+ case "fly":
1282
+ return getFlyTomlTemplate(projectName);
1283
+ case "railway":
1284
+ return getRailwayTomlTemplate(projectName);
1285
+ default:
1286
+ throw new Error(`Unknown deployment platform: ${platform}`);
1287
+ }
1288
+ }
1289
+ function getDeployFilename(platform) {
1290
+ switch (platform) {
1291
+ case "render":
1292
+ return "render.yaml";
1293
+ case "fly":
1294
+ return "fly.toml";
1295
+ case "railway":
1296
+ return "railway.toml";
1297
+ default:
1298
+ throw new Error(`Unknown deployment platform: ${platform}`);
1299
+ }
1300
+ }
1301
+ function getDeployPlatformName(platform) {
1302
+ switch (platform) {
1303
+ case "render":
1304
+ return "Render.com";
1305
+ case "fly":
1306
+ return "Fly.io";
1307
+ case "railway":
1308
+ return "Railway";
1309
+ default:
1310
+ return platform;
1311
+ }
1312
+ }
1313
+
1314
+ // src/cli/commands/new.ts
1315
+ function validateProjectName(name) {
1316
+ if (!name || name.length === 0) {
1317
+ return "Project name is required";
1318
+ }
1319
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
1320
+ return "Project name can only contain letters, numbers, hyphens, and underscores";
1321
+ }
1322
+ if (name.startsWith("-") || name.startsWith("_")) {
1323
+ return "Project name cannot start with a hyphen or underscore";
1324
+ }
1325
+ if (name.length > 100) {
1326
+ return "Project name is too long (max 100 characters)";
1327
+ }
1328
+ return true;
1329
+ }
1330
+ function getPackageJsonTemplate(config) {
1331
+ const dependencies = {
1332
+ bueno: "^0.1.0"
1333
+ };
1334
+ const devDependencies = {
1335
+ "@types/bun": "latest",
1336
+ typescript: "^5.3.0"
1337
+ };
1338
+ if (config.template === "fullstack" || config.template === "default") {
1339
+ dependencies.zod = "^4.0.0";
1340
+ }
1341
+ const scripts = {
1342
+ dev: "bun run --watch server/main.ts",
1343
+ build: "bun build ./server/main.ts --outdir ./dist --target bun",
1344
+ start: "bun run dist/main.js",
1345
+ test: "bun test"
1346
+ };
1347
+ if (config.template === "fullstack") {
1348
+ scripts["dev:frontend"] = "bun run --watch client/index.html";
1349
+ }
1350
+ return JSON.stringify({
1351
+ name: kebabCase(config.name),
1352
+ version: "0.1.0",
1353
+ type: "module",
1354
+ scripts,
1355
+ dependencies,
1356
+ devDependencies
1357
+ }, null, 2);
1358
+ }
1359
+ function getTsConfigTemplate() {
1360
+ return JSON.stringify({
1361
+ compilerOptions: {
1362
+ target: "ESNext",
1363
+ module: "ESNext",
1364
+ moduleResolution: "bundler",
1365
+ strict: true,
1366
+ skipLibCheck: true,
1367
+ esModuleInterop: true,
1368
+ allowSyntheticDefaultImports: true,
1369
+ jsx: "react-jsx",
1370
+ paths: {
1371
+ bueno: ["./node_modules/bueno/dist/index.d.ts"]
1372
+ }
1373
+ },
1374
+ include: ["server/**/*", "client/**/*"],
1375
+ exclude: ["node_modules", "dist"]
1376
+ }, null, 2);
1377
+ }
1378
+ function getMainTemplate(config) {
1379
+ if (config.template === "minimal") {
1380
+ return `import { createServer } from 'bueno';
1381
+
1382
+ const app = createServer();
1383
+
1384
+ app.router.get('/', () => {
1385
+ return { message: 'Hello, Bueno!' };
1386
+ });
1387
+
1388
+ await app.listen(3000);
1389
+ `;
1390
+ }
1391
+ return `import { createApp, Module, Controller, Get, Injectable } from 'bueno';
1392
+ import type { Context } from 'bueno';
1393
+
1394
+ // Services
1395
+ @Injectable()
1396
+ export class AppService {
1397
+ findAll() {
1398
+ return { message: 'Welcome to Bueno!', items: [] };
1399
+ }
1400
+ }
1401
+
1402
+ // Controllers
1403
+ @Controller()
1404
+ export class AppController {
1405
+ constructor(private readonly appService: AppService) {}
1406
+
1407
+ @Get()
1408
+ findAll(ctx: Context) {
1409
+ return this.appService.findAll();
1410
+ }
1411
+
1412
+ @Get('health')
1413
+ health(ctx: Context) {
1414
+ return { status: 'ok', timestamp: new Date().toISOString() };
1415
+ }
1416
+ }
1417
+
1418
+ // Module
1419
+ @Module({
1420
+ controllers: [AppController],
1421
+ providers: [AppService],
1422
+ })
1423
+ export class AppModule {}
1424
+
1425
+ // Bootstrap
1426
+ const app = createApp(AppModule);
1427
+ await app.listen(3000);
1428
+ `;
1429
+ }
1430
+ function getConfigTemplate(config) {
1431
+ const dbConfig = config.database === "sqlite" ? `{ url: 'sqlite:./data.db' }` : `{ url: process.env.DATABASE_URL ?? '${config.database}://localhost/${kebabCase(config.name)}' }`;
1432
+ return `import { defineConfig } from 'bueno';
1433
+
1434
+ export default defineConfig({
1435
+ server: {
1436
+ port: 3000,
1437
+ host: 'localhost',
1438
+ },
1439
+
1440
+ database: ${dbConfig},
1441
+
1442
+ logger: {
1443
+ level: 'info',
1444
+ pretty: true,
1445
+ },
1446
+
1447
+ health: {
1448
+ enabled: true,
1449
+ healthPath: '/health',
1450
+ readyPath: '/ready',
1451
+ },
1452
+ });
1453
+ `;
1454
+ }
1455
+ function getEnvExampleTemplate(config) {
1456
+ if (config.database === "sqlite") {
1457
+ return `# Bueno Environment Variables
1458
+ NODE_ENV=development
1459
+ `;
1460
+ }
1461
+ return `# Bueno Environment Variables
1462
+ NODE_ENV=development
1463
+ DATABASE_URL=${config.database}://user:password@localhost:5432/${kebabCase(config.name)}
1464
+ `;
1465
+ }
1466
+ function getGitignoreTemplate() {
1467
+ return `# Dependencies
1468
+ node_modules/
1469
+
1470
+ # Build output
1471
+ dist/
1472
+
1473
+ # Environment files
1474
+ .env
1475
+ .env.local
1476
+ .env.*.local
1477
+
1478
+ # IDE
1479
+ .idea/
1480
+ .vscode/
1481
+ *.swp
1482
+ *.swo
1483
+
1484
+ # OS
1485
+ .DS_Store
1486
+ Thumbs.db
1487
+
1488
+ # Logs
1489
+ *.log
1490
+ logs/
1491
+
1492
+ # Database
1493
+ *.db
1494
+ *.sqlite
1495
+ *.sqlite3
1496
+
1497
+ # Test coverage
1498
+ coverage/
1499
+ `;
1500
+ }
1501
+ function getReadmeTemplate(config) {
1502
+ return `# ${config.name}
1503
+
1504
+ A Bueno application.
1505
+
1506
+ ## Getting Started
1507
+
1508
+ \`\`\`bash
1509
+ # Install dependencies
1510
+ bun install
1511
+
1512
+ # Start development server
1513
+ bun run dev
1514
+
1515
+ # Build for production
1516
+ bun run build
1517
+
1518
+ # Start production server
1519
+ bun run start
1520
+ \`\`\`
1521
+
1522
+ ## Project Structure
1523
+
1524
+ \`\`\`
1525
+ \u251C\u2500\u2500 server/ # Server-side code
1526
+ \u2502 \u251C\u2500\u2500 main.ts # Entry point
1527
+ \u2502 \u251C\u2500\u2500 modules/ # Feature modules
1528
+ \u2502 \u2514\u2500\u2500 database/ # Database files
1529
+ \u251C\u2500\u2500 client/ # Client-side code (if applicable)
1530
+ \u251C\u2500\u2500 tests/ # Test files
1531
+ \u2514\u2500\u2500 bueno.config.ts # Configuration
1532
+ \`\`\`
1533
+
1534
+ ## Learn More
1535
+
1536
+ - [Bueno Documentation](https://github.com/sivaraj/bueno#readme)
1537
+ - [Bun Documentation](https://bun.sh/docs)
1538
+ `;
1539
+ }
1540
+ async function createProjectFiles(projectPath, config) {
1541
+ const tasks = [];
1542
+ tasks.push({
1543
+ text: "Creating project structure",
1544
+ task: async () => {
1545
+ await createDirectory(joinPaths(projectPath, "server", "modules", "app"));
1546
+ await createDirectory(joinPaths(projectPath, "server", "common", "middleware"));
1547
+ await createDirectory(joinPaths(projectPath, "server", "common", "guards"));
1548
+ await createDirectory(joinPaths(projectPath, "server", "common", "interceptors"));
1549
+ await createDirectory(joinPaths(projectPath, "server", "common", "pipes"));
1550
+ await createDirectory(joinPaths(projectPath, "server", "common", "filters"));
1551
+ await createDirectory(joinPaths(projectPath, "server", "database", "migrations"));
1552
+ await createDirectory(joinPaths(projectPath, "server", "config"));
1553
+ await createDirectory(joinPaths(projectPath, "tests", "unit"));
1554
+ await createDirectory(joinPaths(projectPath, "tests", "integration"));
1555
+ }
1556
+ });
1557
+ tasks.push({
1558
+ text: "Creating package.json",
1559
+ task: async () => {
1560
+ await writeFile(joinPaths(projectPath, "package.json"), getPackageJsonTemplate(config));
1561
+ }
1562
+ });
1563
+ tasks.push({
1564
+ text: "Creating tsconfig.json",
1565
+ task: async () => {
1566
+ await writeFile(joinPaths(projectPath, "tsconfig.json"), getTsConfigTemplate());
1567
+ }
1568
+ });
1569
+ tasks.push({
1570
+ text: "Creating server/main.ts",
1571
+ task: async () => {
1572
+ await writeFile(joinPaths(projectPath, "server", "main.ts"), getMainTemplate(config));
1573
+ }
1574
+ });
1575
+ tasks.push({
1576
+ text: "Creating bueno.config.ts",
1577
+ task: async () => {
1578
+ await writeFile(joinPaths(projectPath, "bueno.config.ts"), getConfigTemplate(config));
1579
+ }
1580
+ });
1581
+ tasks.push({
1582
+ text: "Creating .env.example",
1583
+ task: async () => {
1584
+ await writeFile(joinPaths(projectPath, ".env.example"), getEnvExampleTemplate(config));
1585
+ }
1586
+ });
1587
+ tasks.push({
1588
+ text: "Creating .gitignore",
1589
+ task: async () => {
1590
+ await writeFile(joinPaths(projectPath, ".gitignore"), getGitignoreTemplate());
1591
+ }
1592
+ });
1593
+ tasks.push({
1594
+ text: "Creating README.md",
1595
+ task: async () => {
1596
+ await writeFile(joinPaths(projectPath, "README.md"), getReadmeTemplate(config));
1597
+ }
1598
+ });
1599
+ if (config.docker) {
1600
+ tasks.push({
1601
+ text: "Creating Dockerfile",
1602
+ task: async () => {
1603
+ await writeFile(joinPaths(projectPath, "Dockerfile"), getDockerfileTemplate(config.name, config.database));
1604
+ }
1605
+ });
1606
+ tasks.push({
1607
+ text: "Creating .dockerignore",
1608
+ task: async () => {
1609
+ await writeFile(joinPaths(projectPath, ".dockerignore"), getDockerignoreTemplate());
1610
+ }
1611
+ });
1612
+ tasks.push({
1613
+ text: "Creating docker-compose.yml",
1614
+ task: async () => {
1615
+ await writeFile(joinPaths(projectPath, "docker-compose.yml"), getDockerComposeTemplate(config.name, config.database));
1616
+ }
1617
+ });
1618
+ tasks.push({
1619
+ text: "Creating .env.docker",
1620
+ task: async () => {
1621
+ await writeFile(joinPaths(projectPath, ".env.docker"), getDockerEnvTemplate(config.name, config.database));
1622
+ }
1623
+ });
1624
+ }
1625
+ for (const platform of config.deploy) {
1626
+ const filename = getDeployFilename(platform);
1627
+ tasks.push({
1628
+ text: `Creating ${filename} for ${getDeployPlatformName(platform)}`,
1629
+ task: async () => {
1630
+ await writeFile(joinPaths(projectPath, filename), getDeployTemplate(platform, config.name, config.database));
1631
+ }
1632
+ });
1633
+ }
1634
+ await runTasks(tasks);
1635
+ }
1636
+ async function handleNew(args) {
1637
+ let name = args.positionals[0];
1638
+ const useDefaults = hasFlag(args, "yes") || hasFlag(args, "y");
1639
+ if (!name && isInteractive()) {
1640
+ name = await prompt("Project name:", {
1641
+ validate: validateProjectName
1642
+ });
1643
+ }
1644
+ if (!name) {
1645
+ throw new CLIError("Project name is required. Usage: bueno new <project-name>", "INVALID_ARGS" /* INVALID_ARGS */);
1646
+ }
1647
+ const validation = validateProjectName(name);
1648
+ if (validation !== true) {
1649
+ throw new CLIError(validation, "INVALID_ARGS" /* INVALID_ARGS */);
1650
+ }
1651
+ let template = getOption(args, "template", {
1652
+ name: "template",
1653
+ alias: "t",
1654
+ type: "string",
1655
+ description: ""
1656
+ });
1657
+ let framework = getOption(args, "framework", {
1658
+ name: "framework",
1659
+ alias: "f",
1660
+ type: "string",
1661
+ description: ""
1662
+ });
1663
+ let database = getOption(args, "database", {
1664
+ name: "database",
1665
+ alias: "d",
1666
+ type: "string",
1667
+ description: ""
1668
+ });
1669
+ const skipInstall = hasFlag(args, "skip-install");
1670
+ const skipGit = hasFlag(args, "skip-git");
1671
+ const docker = hasFlag(args, "docker");
1672
+ const deployPlatforms = getOptionValues(args, "deploy");
1673
+ const validPlatforms = ["render", "fly", "railway"];
1674
+ const deploy = [];
1675
+ for (const platform of deployPlatforms) {
1676
+ if (validPlatforms.includes(platform)) {
1677
+ if (!deploy.includes(platform)) {
1678
+ deploy.push(platform);
1679
+ }
1680
+ } else {
1681
+ throw new CLIError(`Invalid deployment platform: ${platform}. Valid options are: ${validPlatforms.join(", ")}`, "INVALID_ARGS" /* INVALID_ARGS */);
1682
+ }
1683
+ }
1684
+ if (!useDefaults && isInteractive()) {
1685
+ if (!template) {
1686
+ template = await select("Select a template:", [
1687
+ { value: "default", name: "Default - Standard project with modules and database" },
1688
+ { value: "minimal", name: "Minimal - Bare minimum project structure" },
1689
+ { value: "fullstack", name: "Fullstack - Full-stack project with SSR and auth" },
1690
+ { value: "api", name: "API - API-only project without frontend" }
1691
+ ], { default: "default" });
1692
+ }
1693
+ if ((template === "fullstack" || template === "default") && !framework) {
1694
+ framework = await select("Select a frontend framework:", [
1695
+ { value: "react", name: "React" },
1696
+ { value: "vue", name: "Vue" },
1697
+ { value: "svelte", name: "Svelte" },
1698
+ { value: "solid", name: "Solid" }
1699
+ ], { default: "react" });
1700
+ }
1701
+ if (!database) {
1702
+ database = await select("Select a database:", [
1703
+ { value: "sqlite", name: "SQLite - Local file-based database" },
1704
+ { value: "postgresql", name: "PostgreSQL - Production-ready relational database" },
1705
+ { value: "mysql", name: "MySQL - Popular relational database" }
1706
+ ], { default: "sqlite" });
1707
+ }
1708
+ }
1709
+ template = template || "default";
1710
+ framework = framework || "react";
1711
+ database = database || "sqlite";
1712
+ const config = {
1713
+ name,
1714
+ template,
1715
+ framework,
1716
+ database,
1717
+ skipInstall,
1718
+ skipGit,
1719
+ docker,
1720
+ deploy
1721
+ };
1722
+ const projectPath = joinPaths(process.cwd(), kebabCase(name));
1723
+ if (await fileExists(projectPath)) {
1724
+ throw new CLIError(`Directory already exists: ${kebabCase(name)}`, "FILE_EXISTS" /* FILE_EXISTS */);
1725
+ }
1726
+ cliConsole.header(`Creating a new Bueno project: ${colors.cyan(name)}`);
1727
+ const rows = [
1728
+ ["Template", template],
1729
+ ["Framework", framework],
1730
+ ["Database", database],
1731
+ ["Docker", docker ? colors.green("Yes") : colors.red("No")],
1732
+ ["Deploy", deploy.length > 0 ? colors.green(deploy.map(getDeployPlatformName).join(", ")) : colors.red("None")],
1733
+ ["Install dependencies", skipInstall ? colors.red("No") : colors.green("Yes")],
1734
+ ["Initialize git", skipGit ? colors.red("No") : colors.green("Yes")]
1735
+ ];
1736
+ printTable(["Setting", "Value"], rows);
1737
+ cliConsole.log("");
1738
+ cliConsole.subheader("Creating project files...");
1739
+ await createProjectFiles(projectPath, config);
1740
+ if (!skipInstall) {
1741
+ cliConsole.subheader("Installing dependencies...");
1742
+ const installSpinner = spinner("Running bun install...");
1743
+ try {
1744
+ const proc = Bun.spawn(["bun", "install"], {
1745
+ cwd: projectPath,
1746
+ stdout: "pipe",
1747
+ stderr: "pipe"
1748
+ });
1749
+ const exitCode = await proc.exited;
1750
+ if (exitCode === 0) {
1751
+ installSpinner.success("Dependencies installed");
1752
+ } else {
1753
+ installSpinner.warn("Failed to install dependencies. Run `bun install` manually.");
1754
+ }
1755
+ } catch {
1756
+ installSpinner.warn("Failed to install dependencies. Run `bun install` manually.");
1757
+ }
1758
+ }
1759
+ if (!skipGit) {
1760
+ cliConsole.subheader("Initializing git repository...");
1761
+ const gitSpinner = spinner("Running git init...");
1762
+ try {
1763
+ const proc = Bun.spawn(["git", "init"], {
1764
+ cwd: projectPath,
1765
+ stdout: "pipe",
1766
+ stderr: "pipe"
1767
+ });
1768
+ const exitCode = await proc.exited;
1769
+ if (exitCode === 0) {
1770
+ Bun.spawn(["git", "add", "."], { cwd: projectPath });
1771
+ Bun.spawn(["git", "commit", "-m", "Initial commit from Bueno CLI"], {
1772
+ cwd: projectPath
1773
+ });
1774
+ gitSpinner.success("Git repository initialized");
1775
+ } else {
1776
+ gitSpinner.warn("Failed to initialize git. Run `git init` manually.");
1777
+ }
1778
+ } catch {
1779
+ gitSpinner.warn("Failed to initialize git. Run `git init` manually.");
1780
+ }
1781
+ }
1782
+ cliConsole.log("");
1783
+ cliConsole.success(`Project created successfully!`);
1784
+ cliConsole.log("");
1785
+ cliConsole.log("Next steps:");
1786
+ cliConsole.log(` ${colors.cyan(`cd ${kebabCase(name)}`)}`);
1787
+ cliConsole.log(` ${colors.cyan("bun run dev")}`);
1788
+ cliConsole.log("");
1789
+ cliConsole.log(`Documentation: ${colors.dim("https://github.com/sivaraj/bueno")}`);
1790
+ }
1791
+ defineCommand({
1792
+ name: "new",
1793
+ description: "Create a new Bueno project",
1794
+ positionals: [
1795
+ {
1796
+ name: "name",
1797
+ required: false,
1798
+ description: "Project name"
1799
+ }
1800
+ ],
1801
+ options: [
1802
+ {
1803
+ name: "template",
1804
+ alias: "t",
1805
+ type: "string",
1806
+ description: "Project template (default, minimal, fullstack, api)"
1807
+ },
1808
+ {
1809
+ name: "framework",
1810
+ alias: "f",
1811
+ type: "string",
1812
+ description: "Frontend framework (react, vue, svelte, solid)"
1813
+ },
1814
+ {
1815
+ name: "database",
1816
+ alias: "d",
1817
+ type: "string",
1818
+ description: "Database driver (sqlite, postgresql, mysql)"
1819
+ },
1820
+ {
1821
+ name: "skip-install",
1822
+ type: "boolean",
1823
+ default: false,
1824
+ description: "Skip dependency installation"
1825
+ },
1826
+ {
1827
+ name: "skip-git",
1828
+ type: "boolean",
1829
+ default: false,
1830
+ description: "Skip git initialization"
1831
+ },
1832
+ {
1833
+ name: "docker",
1834
+ type: "boolean",
1835
+ default: false,
1836
+ description: "Include Docker configuration (Dockerfile, docker-compose.yml)"
1837
+ },
1838
+ {
1839
+ name: "deploy",
1840
+ type: "string",
1841
+ description: "Deployment platform configuration (render, fly, railway). Can be specified multiple times."
1842
+ },
1843
+ {
1844
+ name: "yes",
1845
+ alias: "y",
1846
+ type: "boolean",
1847
+ default: false,
1848
+ description: "Use default options without prompts"
1849
+ }
1850
+ ],
1851
+ examples: [
1852
+ "bueno new my-app",
1853
+ "bueno new my-api --template api",
1854
+ "bueno new my-fullstack --template fullstack --framework react",
1855
+ "bueno new my-project --database postgresql",
1856
+ "bueno new my-app --docker",
1857
+ "bueno new my-app --docker --database postgresql",
1858
+ "bueno new my-app --deploy render",
1859
+ "bueno new my-app --deploy fly",
1860
+ "bueno new my-app --deploy render --deploy fly",
1861
+ "bueno new my-app --docker --deploy render",
1862
+ "bueno new my-app --docker --database postgresql --deploy render",
1863
+ "bueno new my-app -y"
1864
+ ]
1865
+ }, handleNew);
1866
+
1867
+ // src/cli/commands/generate.ts
1868
+ var GENERATOR_ALIASES = {
1869
+ c: "controller",
1870
+ s: "service",
1871
+ m: "module",
1872
+ gu: "guard",
1873
+ i: "interceptor",
1874
+ p: "pipe",
1875
+ f: "filter",
1876
+ d: "dto",
1877
+ mw: "middleware",
1878
+ mi: "migration"
1879
+ };
1880
+ function getTemplate(type) {
1881
+ const templates = {
1882
+ controller: `import { Controller, Get, Post, Put, Delete{{#if path}} } from 'bueno'{{/if}}{{#if service}}, { {{pascalCase service}}Service } from './{{kebabCase service}}.service'{{/if}};
1883
+ import type { Context } from 'bueno';
1884
+
1885
+ @Controller('{{path}}')
1886
+ export class {{pascalCase name}}Controller {
1887
+ {{#if service}}
1888
+ constructor(private readonly {{camelCase service}}Service: {{pascalCase service}}Service) {}
1889
+ {{/if}}
1890
+
1891
+ @Get()
1892
+ async findAll(ctx: Context) {
1893
+ return { message: '{{pascalCase name}} controller' };
1894
+ }
1895
+
1896
+ @Get(':id')
1897
+ async findOne(ctx: Context) {
1898
+ const id = ctx.params.id;
1899
+ return { id, message: '{{pascalCase name}} item' };
1900
+ }
1901
+
1902
+ @Post()
1903
+ async create(ctx: Context) {
1904
+ const body = await ctx.body();
1905
+ return { message: 'Created', data: body };
1906
+ }
1907
+
1908
+ @Put(':id')
1909
+ async update(ctx: Context) {
1910
+ const id = ctx.params.id;
1911
+ const body = await ctx.body();
1912
+ return { id, message: 'Updated', data: body };
1913
+ }
1914
+
1915
+ @Delete(':id')
1916
+ async remove(ctx: Context) {
1917
+ const id = ctx.params.id;
1918
+ return { id, message: 'Deleted' };
1919
+ }
1920
+ }
1921
+ `,
1922
+ service: `import { Injectable } from 'bueno';
1923
+
1924
+ @Injectable()
1925
+ export class {{pascalCase name}}Service {
1926
+ async findAll() {
1927
+ // TODO: Implement findAll
1928
+ return [];
1929
+ }
1930
+
1931
+ async findOne(id: string) {
1932
+ // TODO: Implement findOne
1933
+ return { id };
1934
+ }
1935
+
1936
+ async create(data: unknown) {
1937
+ // TODO: Implement create
1938
+ return data;
1939
+ }
1940
+
1941
+ async update(id: string, data: unknown) {
1942
+ // TODO: Implement update
1943
+ return { id, ...data };
1944
+ }
1945
+
1946
+ async remove(id: string) {
1947
+ // TODO: Implement remove
1948
+ return { id };
1949
+ }
1950
+ }
1951
+ `,
1952
+ module: `import { Module } from 'bueno';
1953
+ import { {{pascalCase name}}Controller } from './{{kebabCase name}}.controller';
1954
+ import { {{pascalCase name}}Service } from './{{kebabCase name}}.service';
1955
+
1956
+ @Module({
1957
+ controllers: [{{pascalCase name}}Controller],
1958
+ providers: [{{pascalCase name}}Service],
1959
+ exports: [{{pascalCase name}}Service],
1960
+ })
1961
+ export class {{pascalCase name}}Module {}
1962
+ `,
1963
+ guard: `import { Injectable, type CanActivate, type Context } from 'bueno';
1964
+
1965
+ @Injectable()
1966
+ export class {{pascalCase name}}Guard implements CanActivate {
1967
+ async canActivate(ctx: Context): Promise<boolean> {
1968
+ // TODO: Implement guard logic
1969
+ // Return true to allow access, false to deny
1970
+ return true;
1971
+ }
1972
+ }
1973
+ `,
1974
+ interceptor: `import { Injectable, type NestInterceptor, type CallHandler, type Context } from 'bueno';
1975
+ import type { Observable } from 'rxjs';
1976
+
1977
+ @Injectable()
1978
+ export class {{pascalCase name}}Interceptor implements NestInterceptor {
1979
+ async intercept(ctx: Context, next: CallHandler): Promise<Observable<unknown>> {
1980
+ // Before handler execution
1981
+ console.log('{{pascalCase name}}Interceptor - Before');
1982
+
1983
+ // Call the handler
1984
+ const result = await next.handle();
1985
+
1986
+ // After handler execution
1987
+ console.log('{{pascalCase name}}Interceptor - After');
1988
+
1989
+ return result;
1990
+ }
1991
+ }
1992
+ `,
1993
+ pipe: `import { Injectable, type PipeTransform, type Context } from 'bueno';
1994
+
1995
+ @Injectable()
1996
+ export class {{pascalCase name}}Pipe implements PipeTransform {
1997
+ async transform(value: unknown, ctx: Context): Promise<unknown> {
1998
+ // TODO: Implement transformation/validation logic
1999
+ // Throw an error to reject the value
2000
+ return value;
2001
+ }
2002
+ }
2003
+ `,
2004
+ filter: `import { Injectable, type ExceptionFilter, type Context } from 'bueno';
2005
+ import type { Response } from 'bueno';
2006
+
2007
+ @Injectable()
2008
+ export class {{pascalCase name}}Filter implements ExceptionFilter {
2009
+ async catch(exception: Error, ctx: Context): Promise<Response> {
2010
+ // TODO: Implement exception handling
2011
+ console.error('{{pascalCase name}}Filter caught:', exception);
2012
+
2013
+ return new Response(
2014
+ JSON.stringify({
2015
+ statusCode: 500,
2016
+ message: 'Internal Server Error',
2017
+ error: exception.message,
2018
+ }),
2019
+ {
2020
+ status: 500,
2021
+ headers: { 'Content-Type': 'application/json' },
2022
+ }
2023
+ );
2024
+ }
2025
+ }
2026
+ `,
2027
+ dto: `/**
2028
+ * {{pascalCase name}} DTO
2029
+ */
2030
+ export interface {{pascalCase name}}Dto {
2031
+ // TODO: Define properties
2032
+ id?: string;
2033
+ createdAt?: Date;
2034
+ updatedAt?: Date;
2035
+ }
2036
+
2037
+ /**
2038
+ * Create {{pascalCase name}} DTO
2039
+ */
2040
+ export interface Create{{pascalCase name}}Dto {
2041
+ // TODO: Define required properties for creation
2042
+ }
2043
+
2044
+ /**
2045
+ * Update {{pascalCase name}} DTO
2046
+ */
2047
+ export interface Update{{pascalCase name}}Dto extends Partial<Create{{pascalCase name}}Dto> {
2048
+ // TODO: Define optional properties for update
2049
+ }
2050
+ `,
2051
+ middleware: `import type { Middleware, Context, Handler } from 'bueno';
2052
+
2053
+ /**
2054
+ * {{pascalCase name}} Middleware
2055
+ */
2056
+ export const {{camelCase name}}Middleware: Middleware = async (
2057
+ ctx: Context,
2058
+ next: Handler
2059
+ ) => {
2060
+ // Before handler execution
2061
+ console.log('{{pascalCase name}}Middleware - Before');
2062
+
2063
+ // Call the next handler
2064
+ const result = await next();
2065
+
2066
+ // After handler execution
2067
+ console.log('{{pascalCase name}}Middleware - After');
2068
+
2069
+ return result;
2070
+ };
2071
+ `,
2072
+ migration: `import { createMigration, type MigrationRunner } from 'bueno';
2073
+
2074
+ export default createMigration('{{migrationId}}', '{{migrationName}}')
2075
+ .up(async (db: MigrationRunner) => {
2076
+ // TODO: Add migration logic
2077
+ // Example:
2078
+ // await db.createTable({
2079
+ // name: '{{tableName}}',
2080
+ // columns: [
2081
+ // { name: 'id', type: 'uuid', primary: true },
2082
+ // { name: 'created_at', type: 'timestamp', default: 'NOW()' },
2083
+ // ],
2084
+ // });
2085
+ })
2086
+ .down(async (db: MigrationRunner) => {
2087
+ // TODO: Add rollback logic
2088
+ // Example:
2089
+ // await db.dropTable('{{tableName}}');
2090
+ });
2091
+ `
2092
+ };
2093
+ return templates[type];
2094
+ }
2095
+ function getFileExtension(type) {
2096
+ return type === "dto" ? ".dto.ts" : ".ts";
2097
+ }
2098
+ function getDefaultDirectory(type) {
2099
+ switch (type) {
2100
+ case "controller":
2101
+ case "service":
2102
+ case "module":
2103
+ case "dto":
2104
+ return "modules";
2105
+ case "guard":
2106
+ return "common/guards";
2107
+ case "interceptor":
2108
+ return "common/interceptors";
2109
+ case "pipe":
2110
+ return "common/pipes";
2111
+ case "filter":
2112
+ return "common/filters";
2113
+ case "middleware":
2114
+ return "common/middleware";
2115
+ case "migration":
2116
+ return "database/migrations";
2117
+ default:
2118
+ return "";
2119
+ }
2120
+ }
2121
+ async function generateFile(config) {
2122
+ const { type, name, module, path: customPath, dryRun, force } = config;
2123
+ const projectRoot = await getProjectRoot();
2124
+ if (!projectRoot) {
2125
+ throw new CLIError("Not in a Bueno project directory", "NOT_FOUND" /* NOT_FOUND */);
2126
+ }
2127
+ const kebabName = kebabCase(name);
2128
+ const defaultDir = getDefaultDirectory(type);
2129
+ let targetDir;
2130
+ if (customPath) {
2131
+ targetDir = joinPaths(projectRoot, customPath);
2132
+ } else if (module) {
2133
+ targetDir = joinPaths(projectRoot, "server", defaultDir, kebabCase(module));
2134
+ } else if (type === "migration") {
2135
+ targetDir = joinPaths(projectRoot, "server", defaultDir);
2136
+ } else {
2137
+ targetDir = joinPaths(projectRoot, "server", defaultDir, kebabName);
2138
+ }
2139
+ const fileName = type === "migration" ? `${generateMigrationId()}_${kebabName}${getFileExtension(type)}` : `${kebabName}${getFileExtension(type)}`;
2140
+ const filePath = joinPaths(targetDir, fileName);
2141
+ if (!force && await fileExists(filePath)) {
2142
+ if (isInteractive()) {
2143
+ const shouldOverwrite = await confirm(`File ${colors.cyan(filePath)} already exists. Overwrite?`, { default: false });
2144
+ if (!shouldOverwrite) {
2145
+ throw new CLIError("File already exists. Use --force to overwrite.", "FILE_EXISTS" /* FILE_EXISTS */);
2146
+ }
2147
+ } else {
2148
+ throw new CLIError(`File already exists: ${filePath}. Use --force to overwrite.`, "FILE_EXISTS" /* FILE_EXISTS */);
2149
+ }
2150
+ }
2151
+ const template = getTemplate(type);
2152
+ const content = processTemplate(template, {
2153
+ name,
2154
+ module: module ?? "",
2155
+ path: customPath ?? kebabName,
2156
+ service: type === "controller" ? name : "",
2157
+ migrationId: generateMigrationId(),
2158
+ migrationName: name,
2159
+ tableName: kebabName
2160
+ });
2161
+ if (dryRun) {
2162
+ cliConsole.log(`
2163
+ ${colors.bold("File:")} ${filePath}`);
2164
+ cliConsole.log(colors.bold("Content:"));
2165
+ cliConsole.log(content);
2166
+ cliConsole.log("");
2167
+ } else {
2168
+ await writeFile(filePath, content);
2169
+ }
2170
+ return filePath;
2171
+ }
2172
+ function generateMigrationId() {
2173
+ const now = new Date;
2174
+ const year = now.getFullYear();
2175
+ const month = String(now.getMonth() + 1).padStart(2, "0");
2176
+ const day = String(now.getDate()).padStart(2, "0");
2177
+ const hour = String(now.getHours()).padStart(2, "0");
2178
+ const minute = String(now.getMinutes()).padStart(2, "0");
2179
+ const second = String(now.getSeconds()).padStart(2, "0");
2180
+ return `${year}${month}${day}${hour}${minute}${second}`;
2181
+ }
2182
+ async function handleGenerate(args) {
2183
+ const typeArg = args.positionals[0];
2184
+ if (!typeArg) {
2185
+ throw new CLIError("Generator type is required. Usage: bueno generate <type> <name>", "INVALID_ARGS" /* INVALID_ARGS */);
2186
+ }
2187
+ const type = GENERATOR_ALIASES[typeArg] ?? typeArg;
2188
+ if (!getTemplate(type)) {
2189
+ throw new CLIError(`Unknown generator type: ${typeArg}. Available types: controller, service, module, guard, interceptor, pipe, filter, dto, middleware, migration`, "INVALID_ARGS" /* INVALID_ARGS */);
2190
+ }
2191
+ const name = args.positionals[1];
2192
+ if (!name) {
2193
+ throw new CLIError("Name is required. Usage: bueno generate <type> <name>", "INVALID_ARGS" /* INVALID_ARGS */);
2194
+ }
2195
+ const config = {
2196
+ type,
2197
+ name,
2198
+ module: getOption(args, "module", {
2199
+ name: "module",
2200
+ type: "string",
2201
+ description: ""
2202
+ }),
2203
+ path: getOption(args, "path", {
2204
+ name: "path",
2205
+ type: "string",
2206
+ description: ""
2207
+ }),
2208
+ dryRun: hasFlag(args, "dry-run"),
2209
+ force: hasFlag(args, "force")
2210
+ };
2211
+ if (!config.dryRun && !await isBuenoProject()) {
2212
+ throw new CLIError("Not in a Bueno project directory. Run this command from a Bueno project.", "NOT_FOUND" /* NOT_FOUND */);
2213
+ }
2214
+ const s = spinner(`Generating ${colors.cyan(type)} ${colors.cyan(name)}...`);
2215
+ try {
2216
+ const filePath = await generateFile(config);
2217
+ if (config.dryRun) {
2218
+ s.info("Dry run complete");
2219
+ } else {
2220
+ s.success(`Created ${colors.green(filePath)}`);
2221
+ }
2222
+ } catch (error) {
2223
+ s.error();
2224
+ throw error;
2225
+ }
2226
+ }
2227
+ defineCommand({
2228
+ name: "generate",
2229
+ alias: "g",
2230
+ description: "Generate code artifacts (controllers, services, modules, etc.)",
2231
+ positionals: [
2232
+ {
2233
+ name: "type",
2234
+ required: true,
2235
+ description: "Type of artifact to generate (controller, service, module, guard, interceptor, pipe, filter, dto, middleware, migration)"
2236
+ },
2237
+ {
2238
+ name: "name",
2239
+ required: true,
2240
+ description: "Name of the artifact"
2241
+ }
2242
+ ],
2243
+ options: [
2244
+ {
2245
+ name: "module",
2246
+ alias: "m",
2247
+ type: "string",
2248
+ description: "Parent module to register with"
2249
+ },
2250
+ {
2251
+ name: "path",
2252
+ type: "string",
2253
+ description: "Custom path for controller routes"
2254
+ },
2255
+ {
2256
+ name: "dry-run",
2257
+ type: "boolean",
2258
+ default: false,
2259
+ description: "Show what would be created without writing"
2260
+ },
2261
+ {
2262
+ name: "force",
2263
+ type: "boolean",
2264
+ default: false,
2265
+ description: "Overwrite existing files"
2266
+ }
2267
+ ],
2268
+ examples: [
2269
+ "bueno generate controller users",
2270
+ "bueno g service auth",
2271
+ "bueno g module posts",
2272
+ "bueno g guard auth-guard --module auth",
2273
+ "bueno g dto create-user --module users"
2274
+ ]
2275
+ }, handleGenerate);
2276
+
2277
+ // src/cli/commands/migration.ts
2278
+ function generateMigrationId2() {
2279
+ const now = new Date;
2280
+ const year = now.getFullYear();
2281
+ const month = String(now.getMonth() + 1).padStart(2, "0");
2282
+ const day = String(now.getDate()).padStart(2, "0");
2283
+ const hour = String(now.getHours()).padStart(2, "0");
2284
+ const minute = String(now.getMinutes()).padStart(2, "0");
2285
+ const second = String(now.getSeconds()).padStart(2, "0");
2286
+ return `${year}${month}${day}${hour}${minute}${second}`;
2287
+ }
2288
+ async function getMigrationsDir() {
2289
+ const projectRoot = await getProjectRoot();
2290
+ if (!projectRoot) {
2291
+ throw new CLIError("Not in a project directory", "NOT_FOUND" /* NOT_FOUND */);
2292
+ }
2293
+ const possibleDirs = [
2294
+ joinPaths(projectRoot, "server", "database", "migrations"),
2295
+ joinPaths(projectRoot, "database", "migrations"),
2296
+ joinPaths(projectRoot, "migrations")
2297
+ ];
2298
+ for (const dir of possibleDirs) {
2299
+ if (await fileExists(dir)) {
2300
+ return dir;
2301
+ }
2302
+ }
2303
+ return possibleDirs[0] ?? "";
2304
+ }
2305
+ async function getMigrationFiles(dir) {
2306
+ if (!await fileExists(dir)) {
2307
+ return [];
2308
+ }
2309
+ const files = await listFiles(dir, {
2310
+ recursive: false,
2311
+ pattern: /\.ts$/
2312
+ });
2313
+ return files.sort();
2314
+ }
2315
+ function parseMigrationFile(filename) {
2316
+ const match = filename.match(/^(\d+)_(.+)\.ts$/);
2317
+ if (!match || !match[1] || !match[2]) {
2318
+ return { id: filename, name: filename };
2319
+ }
2320
+ return { id: match[1], name: match[2] };
2321
+ }
2322
+ async function createMigration(name, dryRun) {
2323
+ const migrationsDir = await getMigrationsDir();
2324
+ const id = generateMigrationId2();
2325
+ const kebabName = name.toLowerCase().replace(/\s+/g, "-");
2326
+ const fileName = `${id}_${kebabName}.ts`;
2327
+ const filePath = joinPaths(migrationsDir, fileName);
2328
+ const template = `import { createMigration, type MigrationRunner } from 'bueno';
2329
+
2330
+ export default createMigration('${id}', '${kebabName}')
2331
+ .up(async (db: MigrationRunner) => {
2332
+ // TODO: Add migration logic
2333
+ // Example:
2334
+ // await db.createTable({
2335
+ // name: '${kebabName}',
2336
+ // columns: [
2337
+ // { name: 'id', type: 'uuid', primary: true },
2338
+ // { name: 'created_at', type: 'timestamp', default: 'NOW()' },
2339
+ // ],
2340
+ // });
2341
+ })
2342
+ .down(async (db: MigrationRunner) => {
2343
+ // TODO: Add rollback logic
2344
+ // Example:
2345
+ // await db.dropTable('${kebabName}');
2346
+ });
2347
+ `;
2348
+ if (dryRun) {
2349
+ cliConsole.log(`
2350
+ ${colors.bold("File:")} ${filePath}`);
2351
+ cliConsole.log(colors.bold("Content:"));
2352
+ cliConsole.log(template);
2353
+ cliConsole.log("");
2354
+ return filePath;
2355
+ }
2356
+ await writeFile(filePath, template);
2357
+ return filePath;
2358
+ }
2359
+ async function showStatus() {
2360
+ const migrationsDir = await getMigrationsDir();
2361
+ const files = await getMigrationFiles(migrationsDir);
2362
+ if (files.length === 0) {
2363
+ cliConsole.info("No migrations found");
2364
+ return;
2365
+ }
2366
+ cliConsole.header("Migration Status");
2367
+ const rows = files.map((file) => {
2368
+ const info = parseMigrationFile(file.split("/").pop() ?? "");
2369
+ return [info.id, info.name, colors.yellow("Pending")];
2370
+ });
2371
+ printTable(["ID", "Name", "Status"], rows);
2372
+ cliConsole.log("");
2373
+ cliConsole.log(`Total: ${files.length} migration(s)`);
2374
+ }
2375
+ async function handleMigration(args) {
2376
+ const action = args.positionals[0];
2377
+ if (!action) {
2378
+ throw new CLIError("Action is required. Usage: bueno migration <action>", "INVALID_ARGS" /* INVALID_ARGS */);
2379
+ }
2380
+ const validActions = ["create", "up", "down", "reset", "refresh", "status"];
2381
+ if (!validActions.includes(action)) {
2382
+ throw new CLIError(`Unknown action: ${action}. Valid actions: ${validActions.join(", ")}`, "INVALID_ARGS" /* INVALID_ARGS */);
2383
+ }
2384
+ const dryRun = hasFlag(args, "dry-run");
2385
+ const steps = getOption(args, "steps", {
2386
+ name: "steps",
2387
+ alias: "n",
2388
+ type: "number",
2389
+ default: 1,
2390
+ description: ""
2391
+ });
2392
+ if (action !== "create" || !dryRun) {
2393
+ if (!await isBuenoProject()) {
2394
+ throw new CLIError("Not in a Bueno project directory. Run this command from a Bueno project.", "NOT_FOUND" /* NOT_FOUND */);
2395
+ }
2396
+ }
2397
+ switch (action) {
2398
+ case "create": {
2399
+ const name = args.positionals[1];
2400
+ if (!name) {
2401
+ throw new CLIError("Migration name is required. Usage: bueno migration create <name>", "INVALID_ARGS" /* INVALID_ARGS */);
2402
+ }
2403
+ const s = spinner(`Creating migration ${colors.cyan(name)}...`);
2404
+ try {
2405
+ const filePath = await createMigration(name, dryRun);
2406
+ if (dryRun) {
2407
+ s.info("Dry run complete");
2408
+ } else {
2409
+ s.success(`Created ${colors.green(filePath)}`);
2410
+ }
2411
+ } catch (error) {
2412
+ s.error();
2413
+ throw error;
2414
+ }
2415
+ break;
2416
+ }
2417
+ case "up": {
2418
+ cliConsole.info("Running pending migrations...");
2419
+ cliConsole.log("");
2420
+ cliConsole.warn("Migration execution requires database connection. Use the MigrationRunner in your application code.");
2421
+ cliConsole.log("");
2422
+ cliConsole.log("Example:");
2423
+ cliConsole.log(colors.cyan(`
2424
+ import { createMigrationRunner, loadMigrations } from 'bueno';
2425
+ import { db } from './database';
2426
+
2427
+ const runner = createMigrationRunner(db);
2428
+ const migrations = await loadMigrations('./database/migrations');
2429
+ await runner.migrate(migrations);
2430
+ `));
2431
+ break;
2432
+ }
2433
+ case "down": {
2434
+ cliConsole.info(`Rolling back ${steps} migration(s)...`);
2435
+ cliConsole.log("");
2436
+ cliConsole.warn("Migration rollback requires database connection. Use the MigrationRunner in your application code.");
2437
+ cliConsole.log("");
2438
+ cliConsole.log("Example:");
2439
+ cliConsole.log(colors.cyan(`
2440
+ import { createMigrationRunner, loadMigrations } from 'bueno';
2441
+ import { db } from './database';
2442
+
2443
+ const runner = createMigrationRunner(db);
2444
+ const migrations = await loadMigrations('./database/migrations');
2445
+ await runner.rollback(migrations, ${steps});
2446
+ `));
2447
+ break;
2448
+ }
2449
+ case "reset": {
2450
+ cliConsole.info("Rolling back all migrations...");
2451
+ cliConsole.log("");
2452
+ cliConsole.warn("Migration reset requires database connection. Use the MigrationRunner in your application code.");
2453
+ cliConsole.log("");
2454
+ cliConsole.log("Example:");
2455
+ cliConsole.log(colors.cyan(`
2456
+ import { createMigrationRunner, loadMigrations } from 'bueno';
2457
+ import { db } from './database';
2458
+
2459
+ const runner = createMigrationRunner(db);
2460
+ const migrations = await loadMigrations('./database/migrations');
2461
+ await runner.reset(migrations);
2462
+ `));
2463
+ break;
2464
+ }
2465
+ case "refresh": {
2466
+ cliConsole.info("Refreshing all migrations...");
2467
+ cliConsole.log("");
2468
+ cliConsole.warn("Migration refresh requires database connection. Use the MigrationRunner in your application code.");
2469
+ cliConsole.log("");
2470
+ cliConsole.log("Example:");
2471
+ cliConsole.log(colors.cyan(`
2472
+ import { createMigrationRunner, loadMigrations } from 'bueno';
2473
+ import { db } from './database';
2474
+
2475
+ const runner = createMigrationRunner(db);
2476
+ const migrations = await loadMigrations('./database/migrations');
2477
+ await runner.refresh(migrations);
2478
+ `));
2479
+ break;
2480
+ }
2481
+ case "status": {
2482
+ await showStatus();
2483
+ break;
2484
+ }
2485
+ }
2486
+ }
2487
+ defineCommand({
2488
+ name: "migration",
2489
+ description: "Manage database migrations",
2490
+ positionals: [
2491
+ {
2492
+ name: "action",
2493
+ required: true,
2494
+ description: "Action to perform (create, up, down, reset, refresh, status)"
2495
+ },
2496
+ {
2497
+ name: "name",
2498
+ required: false,
2499
+ description: "Migration name (required for create action)"
2500
+ }
2501
+ ],
2502
+ options: [
2503
+ {
2504
+ name: "steps",
2505
+ alias: "n",
2506
+ type: "number",
2507
+ default: 1,
2508
+ description: "Number of migrations to rollback"
2509
+ },
2510
+ {
2511
+ name: "dry-run",
2512
+ type: "boolean",
2513
+ default: false,
2514
+ description: "Show what would happen without executing"
2515
+ }
2516
+ ],
2517
+ examples: [
2518
+ "bueno migration create add-users-table",
2519
+ "bueno migration up",
2520
+ "bueno migration down --steps 3",
2521
+ "bueno migration reset",
2522
+ "bueno migration refresh",
2523
+ "bueno migration status"
2524
+ ]
2525
+ }, handleMigration);
2526
+
2527
+ // src/cli/commands/dev.ts
2528
+ async function findEntryPoint(projectRoot) {
2529
+ const possibleEntries = [
2530
+ "server/main.ts",
2531
+ "src/main.ts",
2532
+ "src/index.ts",
2533
+ "main.ts",
2534
+ "index.ts",
2535
+ "server.ts",
2536
+ "app.ts"
2537
+ ];
2538
+ for (const entry of possibleEntries) {
2539
+ const entryPath = joinPaths(projectRoot, entry);
2540
+ if (await fileExists(entryPath)) {
2541
+ return entry;
2542
+ }
2543
+ }
2544
+ return null;
2545
+ }
2546
+ async function handleDev(args) {
2547
+ const port = getOption(args, "port", {
2548
+ name: "port",
2549
+ alias: "p",
2550
+ type: "number",
2551
+ default: 3000,
2552
+ description: ""
2553
+ });
2554
+ const host = getOption(args, "host", {
2555
+ name: "host",
2556
+ alias: "H",
2557
+ type: "string",
2558
+ default: "localhost",
2559
+ description: ""
2560
+ });
2561
+ const hmr = !hasFlag(args, "no-hmr");
2562
+ const watch = !hasFlag(args, "no-watch");
2563
+ const openBrowser = hasFlag(args, "open") || hasFlag(args, "o");
2564
+ const configPath = getOption(args, "config", {
2565
+ name: "config",
2566
+ alias: "c",
2567
+ type: "string",
2568
+ description: ""
2569
+ });
2570
+ const projectRoot = await getProjectRoot();
2571
+ if (!projectRoot) {
2572
+ throw new CLIError("Not in a project directory. Run this command from a Bueno project.", "NOT_FOUND" /* NOT_FOUND */);
2573
+ }
2574
+ if (!await isBuenoProject()) {
2575
+ throw new CLIError("Not a Bueno project. Make sure you have a bueno.config.ts or bueno in your dependencies.", "NOT_FOUND" /* NOT_FOUND */);
2576
+ }
2577
+ const entryPoint = await findEntryPoint(projectRoot);
2578
+ if (!entryPoint) {
2579
+ throw new CLIError("Could not find entry point. Make sure you have a main.ts or index.ts file.", "FILE_NOT_FOUND" /* FILE_NOT_FOUND */);
2580
+ }
2581
+ const bunArgs = [];
2582
+ if (watch) {
2583
+ bunArgs.push("--watch");
2584
+ }
2585
+ if (hmr) {
2586
+ cliConsole.debug("HMR enabled");
2587
+ }
2588
+ bunArgs.push(entryPoint);
2589
+ const env = {
2590
+ NODE_ENV: "development",
2591
+ PORT: String(port),
2592
+ HOST: host
2593
+ };
2594
+ if (configPath) {
2595
+ env.BUENO_CONFIG = configPath;
2596
+ }
2597
+ cliConsole.header("Starting Development Server");
2598
+ cliConsole.log(`${colors.bold("Entry:")} ${entryPoint}`);
2599
+ cliConsole.log(`${colors.bold("Port:")} ${port}`);
2600
+ cliConsole.log(`${colors.bold("Host:")} ${host}`);
2601
+ cliConsole.log(`${colors.bold("Watch:")} ${watch ? colors.green("enabled") : colors.red("disabled")}`);
2602
+ cliConsole.log(`${colors.bold("HMR:")} ${hmr ? colors.green("enabled") : colors.red("disabled")}`);
2603
+ cliConsole.log("");
2604
+ const s = spinner("Starting development server...");
2605
+ try {
2606
+ const proc = Bun.spawn(["bun", "run", ...bunArgs], {
2607
+ cwd: projectRoot,
2608
+ env: { ...process.env, ...env },
2609
+ stdout: "inherit",
2610
+ stderr: "inherit"
2611
+ });
2612
+ s.success(`Development server running at ${colors.cyan(`http://${host}:${port}`)}`);
2613
+ if (openBrowser) {
2614
+ const openCommand = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
2615
+ Bun.spawn([openCommand, `http://${host}:${port}`], {
2616
+ cwd: projectRoot
2617
+ });
2618
+ }
2619
+ const exitCode = await proc.exited;
2620
+ if (exitCode !== 0 && exitCode !== null) {
2621
+ cliConsole.error(`Server exited with code ${exitCode}`);
2622
+ process.exit(exitCode);
2623
+ }
2624
+ } catch (error) {
2625
+ s.error();
2626
+ throw error;
2627
+ }
2628
+ }
2629
+ defineCommand({
2630
+ name: "dev",
2631
+ description: "Start the development server with hot reload",
2632
+ options: [
2633
+ {
2634
+ name: "port",
2635
+ alias: "p",
2636
+ type: "number",
2637
+ default: 3000,
2638
+ description: "Server port"
2639
+ },
2640
+ {
2641
+ name: "host",
2642
+ alias: "H",
2643
+ type: "string",
2644
+ default: "localhost",
2645
+ description: "Server hostname"
2646
+ },
2647
+ {
2648
+ name: "no-hmr",
2649
+ type: "boolean",
2650
+ default: false,
2651
+ description: "Disable hot module replacement"
2652
+ },
2653
+ {
2654
+ name: "no-watch",
2655
+ type: "boolean",
2656
+ default: false,
2657
+ description: "Disable file watching"
2658
+ },
2659
+ {
2660
+ name: "open",
2661
+ alias: "o",
2662
+ type: "boolean",
2663
+ default: false,
2664
+ description: "Open browser on start"
2665
+ },
2666
+ {
2667
+ name: "config",
2668
+ alias: "c",
2669
+ type: "string",
2670
+ description: "Path to config file"
2671
+ }
2672
+ ],
2673
+ examples: [
2674
+ "bueno dev",
2675
+ "bueno dev --port 4000",
2676
+ "bueno dev --host 0.0.0.0",
2677
+ "bueno dev --no-hmr",
2678
+ "bueno dev --open"
2679
+ ]
2680
+ }, handleDev);
2681
+
2682
+ // src/cli/commands/build.ts
2683
+ async function findEntryPoint2(projectRoot) {
2684
+ const possibleEntries = [
2685
+ "server/main.ts",
2686
+ "src/main.ts",
2687
+ "src/index.ts",
2688
+ "main.ts",
2689
+ "index.ts",
2690
+ "server.ts",
2691
+ "app.ts"
2692
+ ];
2693
+ for (const entry of possibleEntries) {
2694
+ const entryPath = joinPaths(projectRoot, entry);
2695
+ if (await fileExists(entryPath)) {
2696
+ return entry;
2697
+ }
2698
+ }
2699
+ return null;
2700
+ }
2701
+ async function handleBuild(args) {
2702
+ const target = getOption(args, "target", {
2703
+ name: "target",
2704
+ alias: "t",
2705
+ type: "string",
2706
+ default: "bun",
2707
+ description: ""
2708
+ });
2709
+ const outDir = getOption(args, "outdir", {
2710
+ name: "outdir",
2711
+ alias: "o",
2712
+ type: "string",
2713
+ default: "./dist",
2714
+ description: ""
2715
+ });
2716
+ const minify = !hasFlag(args, "no-minify");
2717
+ const sourcemap = hasFlag(args, "sourcemap");
2718
+ const analyze = hasFlag(args, "analyze");
2719
+ const configPath = getOption(args, "config", {
2720
+ name: "config",
2721
+ alias: "c",
2722
+ type: "string",
2723
+ description: ""
2724
+ });
2725
+ const compile = hasFlag(args, "compile");
2726
+ const crossCompile = getOption(args, "cross-compile", {
2727
+ name: "cross-compile",
2728
+ type: "string",
2729
+ description: ""
2730
+ });
2731
+ const executableName = getOption(args, "executable-name", {
2732
+ name: "executable-name",
2733
+ type: "string",
2734
+ default: "main",
2735
+ description: ""
2736
+ });
2737
+ if (crossCompile) {
2738
+ const validCrossCompileTargets = [
2739
+ "linux-x64",
2740
+ "linux-arm64",
2741
+ "windows-x64",
2742
+ "darwin-x64",
2743
+ "darwin-arm64"
2744
+ ];
2745
+ if (!validCrossCompileTargets.includes(crossCompile)) {
2746
+ throw new CLIError(`Invalid cross-compile target: ${crossCompile}. Valid targets: ${validCrossCompileTargets.join(", ")}`, "INVALID_ARGS" /* INVALID_ARGS */);
2747
+ }
2748
+ }
2749
+ if (crossCompile && !compile) {
2750
+ throw new CLIError("--cross-compile requires --compile flag", "INVALID_ARGS" /* INVALID_ARGS */);
2751
+ }
2752
+ const validTargets = ["bun", "node", "standalone"];
2753
+ if (!validTargets.includes(target)) {
2754
+ throw new CLIError(`Invalid target: ${target}. Valid targets: ${validTargets.join(", ")}`, "INVALID_ARGS" /* INVALID_ARGS */);
2755
+ }
2756
+ const projectRoot = await getProjectRoot();
2757
+ if (!projectRoot) {
2758
+ throw new CLIError("Not in a project directory. Run this command from a Bueno project.", "NOT_FOUND" /* NOT_FOUND */);
2759
+ }
2760
+ if (!await isBuenoProject()) {
2761
+ throw new CLIError("Not a Bueno project. Make sure you have a bueno.config.ts or bueno in your dependencies.", "NOT_FOUND" /* NOT_FOUND */);
2762
+ }
2763
+ const entryPoint = await findEntryPoint2(projectRoot);
2764
+ if (!entryPoint) {
2765
+ throw new CLIError("Could not find entry point. Make sure you have a main.ts or index.ts file.", "FILE_NOT_FOUND" /* FILE_NOT_FOUND */);
2766
+ }
2767
+ if (compile) {
2768
+ cliConsole.header("Compiling Single-File Executable");
2769
+ } else {
2770
+ cliConsole.header("Building for Production");
2771
+ }
2772
+ cliConsole.log(`${colors.bold("Entry:")} ${entryPoint}`);
2773
+ cliConsole.log(`${colors.bold("Target:")} ${target}`);
2774
+ if (compile) {
2775
+ cliConsole.log(`${colors.bold("Compile:")} ${colors.green("enabled")}`);
2776
+ if (crossCompile) {
2777
+ cliConsole.log(`${colors.bold("Cross-compile:")} ${colors.cyan(crossCompile)}`);
2778
+ }
2779
+ cliConsole.log(`${colors.bold("Executable:")} ${executableName}`);
2780
+ }
2781
+ cliConsole.log(`${colors.bold("Output:")} ${outDir}`);
2782
+ cliConsole.log(`${colors.bold("Minify:")} ${minify ? colors.green("enabled") : colors.red("disabled")}`);
2783
+ cliConsole.log(`${colors.bold("Sourcemap:")} ${sourcemap ? colors.green("enabled") : colors.red("disabled")}`);
2784
+ cliConsole.log("");
2785
+ const startTime = Date.now();
2786
+ const s = spinner("Building...");
2787
+ try {
2788
+ const fullOutDir = joinPaths(projectRoot, outDir);
2789
+ if (await fileExists(fullOutDir)) {
2790
+ await deleteDirectory(fullOutDir);
2791
+ }
2792
+ if (compile) {
2793
+ const isWindows = crossCompile === "windows-x64";
2794
+ const executableFileName = isWindows ? `${executableName}.exe` : executableName;
2795
+ const executablePath = joinPaths(fullOutDir, executableFileName);
2796
+ const buildOptions = {
2797
+ entrypoints: [joinPaths(projectRoot, entryPoint)],
2798
+ outdir: fullOutDir,
2799
+ target: crossCompile || "bun",
2800
+ minify,
2801
+ sourcemap: sourcemap ? "external" : undefined,
2802
+ naming: executableFileName,
2803
+ compile: true,
2804
+ define: {
2805
+ "process.env.NODE_ENV": '"production"'
2806
+ }
2807
+ };
2808
+ const buildResult2 = await Bun.build(buildOptions);
2809
+ if (!buildResult2.success) {
2810
+ s.error();
2811
+ for (const error of buildResult2.logs) {
2812
+ cliConsole.error(error.message);
2813
+ }
2814
+ throw new CLIError("Compile failed", "TEMPLATE_ERROR" /* TEMPLATE_ERROR */);
2815
+ }
2816
+ const elapsed2 = Date.now() - startTime;
2817
+ s.success(`Compile completed in ${formatDuration(elapsed2)}`);
2818
+ cliConsole.log("");
2819
+ cliConsole.log(`${colors.bold("Output Executable:")}`);
2820
+ const fs2 = __require("fs");
2821
+ let executableSize = 0;
2822
+ try {
2823
+ const stat = fs2.statSync(executablePath);
2824
+ executableSize = stat.size;
2825
+ } catch (e) {
2826
+ if (buildResult2.outputs.length > 0) {
2827
+ executableSize = buildResult2.outputs[0].size;
2828
+ }
2829
+ }
2830
+ cliConsole.log(` ${colors.cyan(executablePath.replace(projectRoot, "."))} ${colors.dim(`(${formatSize(executableSize)})`)}`);
2831
+ cliConsole.log("");
2832
+ cliConsole.success("Single-file executable created successfully!");
2833
+ if (crossCompile) {
2834
+ cliConsole.log(`${colors.bold("Target Platform:")} ${crossCompile}`);
2835
+ }
2836
+ cliConsole.log("");
2837
+ cliConsole.info("You can run the executable directly:");
2838
+ if (isWindows) {
2839
+ cliConsole.log(colors.cyan(` .${outDir}/${executableFileName}`));
2840
+ } else {
2841
+ cliConsole.log(colors.cyan(` .${outDir}/${executableFileName}`));
2842
+ }
2843
+ if (analyze) {
2844
+ cliConsole.log("");
2845
+ cliConsole.header("Bundle Analysis");
2846
+ cliConsole.log("Output:");
2847
+ for (const entry of buildResult2.outputs) {
2848
+ cliConsole.log(` ${entry.path} (${formatSize(entry.size)})`);
2849
+ }
2850
+ }
2851
+ return;
2852
+ }
2853
+ const buildResult = await Bun.build({
2854
+ entrypoints: [joinPaths(projectRoot, entryPoint)],
2855
+ outdir: fullOutDir,
2856
+ target: target === "node" ? "node" : "bun",
2857
+ minify,
2858
+ sourcemap: sourcemap ? "external" : undefined,
2859
+ splitting: true,
2860
+ format: "esm",
2861
+ external: target === "standalone" ? [] : ["bun:*"],
2862
+ define: {
2863
+ "process.env.NODE_ENV": '"production"'
2864
+ }
2865
+ });
2866
+ if (!buildResult.success) {
2867
+ s.error();
2868
+ for (const error of buildResult.logs) {
2869
+ cliConsole.error(error.message);
2870
+ }
2871
+ throw new CLIError("Build failed", "TEMPLATE_ERROR" /* TEMPLATE_ERROR */);
2872
+ }
2873
+ const elapsed = Date.now() - startTime;
2874
+ const outputFiles = await listFiles(fullOutDir, { recursive: true });
2875
+ const totalSize = outputFiles.reduce((acc, file) => {
2876
+ const stat = __require("fs").statSync(file);
2877
+ return acc + stat.size;
2878
+ }, 0);
2879
+ s.success(`Build completed in ${formatDuration(elapsed)}`);
2880
+ cliConsole.log("");
2881
+ cliConsole.log(`${colors.bold("Output Files:")}`);
2882
+ for (const file of outputFiles.slice(0, 10)) {
2883
+ const stat = __require("fs").statSync(file);
2884
+ const relativePath = file.replace(projectRoot, ".");
2885
+ cliConsole.log(` ${colors.dim(relativePath)} ${colors.dim(`(${formatSize(stat.size)})`)}`);
2886
+ }
2887
+ if (outputFiles.length > 10) {
2888
+ cliConsole.log(` ${colors.dim(`... and ${outputFiles.length - 10} more files`)}`);
2889
+ }
2890
+ cliConsole.log("");
2891
+ cliConsole.log(`${colors.bold("Total Size:")} ${formatSize(totalSize)}`);
2892
+ if (analyze) {
2893
+ cliConsole.log("");
2894
+ cliConsole.header("Bundle Analysis");
2895
+ cliConsole.log("Entry points:");
2896
+ for (const entry of buildResult.outputs) {
2897
+ cliConsole.log(` ${entry.path} (${formatSize(entry.size)})`);
2898
+ }
2899
+ }
2900
+ if (target === "standalone") {
2901
+ cliConsole.log("");
2902
+ cliConsole.info("Standalone bundle created. You can run it with:");
2903
+ cliConsole.log(colors.cyan(` bun .${outDir}/${entryPoint.replace(".ts", ".js")}`));
2904
+ }
2905
+ } catch (error) {
2906
+ s.error();
2907
+ throw error;
2908
+ }
2909
+ }
2910
+ defineCommand({
2911
+ name: "build",
2912
+ description: "Build the application for production",
2913
+ options: [
2914
+ {
2915
+ name: "target",
2916
+ alias: "t",
2917
+ type: "string",
2918
+ default: "bun",
2919
+ description: "Build target (bun, node, standalone)"
2920
+ },
2921
+ {
2922
+ name: "outdir",
2923
+ alias: "o",
2924
+ type: "string",
2925
+ default: "./dist",
2926
+ description: "Output directory"
2927
+ },
2928
+ {
2929
+ name: "no-minify",
2930
+ type: "boolean",
2931
+ default: false,
2932
+ description: "Disable minification"
2933
+ },
2934
+ {
2935
+ name: "sourcemap",
2936
+ type: "boolean",
2937
+ default: false,
2938
+ description: "Generate source maps"
2939
+ },
2940
+ {
2941
+ name: "analyze",
2942
+ type: "boolean",
2943
+ default: false,
2944
+ description: "Analyze bundle size"
2945
+ },
2946
+ {
2947
+ name: "config",
2948
+ alias: "c",
2949
+ type: "string",
2950
+ description: "Path to config file"
2951
+ },
2952
+ {
2953
+ name: "compile",
2954
+ type: "boolean",
2955
+ default: false,
2956
+ description: "Create a single-file executable using Bun compile"
2957
+ },
2958
+ {
2959
+ name: "cross-compile",
2960
+ type: "string",
2961
+ description: "Cross-compile for different platforms (linux-x64, linux-arm64, windows-x64, darwin-x64, darwin-arm64)"
2962
+ },
2963
+ {
2964
+ name: "executable-name",
2965
+ type: "string",
2966
+ default: "main",
2967
+ description: "Custom name for the output executable (default: main)"
2968
+ }
2969
+ ],
2970
+ examples: [
2971
+ "bueno build",
2972
+ "bueno build --target node",
2973
+ "bueno build --target standalone",
2974
+ "bueno build --sourcemap",
2975
+ "bueno build --analyze",
2976
+ "bueno build --compile",
2977
+ "bueno build --compile --outdir ./bin",
2978
+ "bueno build --compile --cross-compile linux-x64",
2979
+ "bueno build --compile --executable-name myapp"
2980
+ ]
2981
+ }, handleBuild);
2982
+
2983
+ // src/cli/commands/start.ts
2984
+ async function findEntryPoint3(projectRoot) {
2985
+ const possibleBuiltEntries = [
2986
+ "dist/index.js",
2987
+ "dist/main.js",
2988
+ "dist/server.js",
2989
+ "dist/app.js"
2990
+ ];
2991
+ for (const entry of possibleBuiltEntries) {
2992
+ const entryPath = joinPaths(projectRoot, entry);
2993
+ if (await fileExists(entryPath)) {
2994
+ return entry;
2995
+ }
2996
+ }
2997
+ const possibleSourceEntries = [
2998
+ "server/main.ts",
2999
+ "src/main.ts",
3000
+ "src/index.ts",
3001
+ "main.ts",
3002
+ "index.ts",
3003
+ "server.ts",
3004
+ "app.ts"
3005
+ ];
3006
+ for (const entry of possibleSourceEntries) {
3007
+ const entryPath = joinPaths(projectRoot, entry);
3008
+ if (await fileExists(entryPath)) {
3009
+ return entry;
3010
+ }
3011
+ }
3012
+ return null;
3013
+ }
3014
+ async function handleStart(args) {
3015
+ const port = getOption(args, "port", {
3016
+ name: "port",
3017
+ alias: "p",
3018
+ type: "number",
3019
+ default: 3000,
3020
+ description: ""
3021
+ });
3022
+ const host = getOption(args, "host", {
3023
+ name: "host",
3024
+ alias: "H",
3025
+ type: "string",
3026
+ default: "0.0.0.0",
3027
+ description: ""
3028
+ });
3029
+ const workers = getOption(args, "workers", {
3030
+ name: "workers",
3031
+ alias: "w",
3032
+ type: "string",
3033
+ default: "auto",
3034
+ description: ""
3035
+ });
3036
+ const configPath = getOption(args, "config", {
3037
+ name: "config",
3038
+ alias: "c",
3039
+ type: "string",
3040
+ description: ""
3041
+ });
3042
+ const projectRoot = await getProjectRoot();
3043
+ if (!projectRoot) {
3044
+ throw new CLIError("Not in a project directory. Run this command from a Bueno project.", "NOT_FOUND" /* NOT_FOUND */);
3045
+ }
3046
+ if (!await isBuenoProject()) {
3047
+ throw new CLIError("Not a Bueno project. Make sure you have a bueno.config.ts or bueno in your dependencies.", "NOT_FOUND" /* NOT_FOUND */);
3048
+ }
3049
+ const entryPoint = await findEntryPoint3(projectRoot);
3050
+ if (!entryPoint) {
3051
+ throw new CLIError("Could not find entry point. Make sure you have built the application or have a main.ts file.", "FILE_NOT_FOUND" /* FILE_NOT_FOUND */);
3052
+ }
3053
+ cliConsole.header("Starting Production Server");
3054
+ cliConsole.log(`${colors.bold("Entry:")} ${entryPoint}`);
3055
+ cliConsole.log(`${colors.bold("Port:")} ${port}`);
3056
+ cliConsole.log(`${colors.bold("Host:")} ${host}`);
3057
+ cliConsole.log(`${colors.bold("Workers:")} ${workers}`);
3058
+ cliConsole.log("");
3059
+ const env = {
3060
+ NODE_ENV: "production",
3061
+ PORT: String(port),
3062
+ HOST: host
3063
+ };
3064
+ if (configPath) {
3065
+ env.BUENO_CONFIG = configPath;
3066
+ }
3067
+ const s = spinner("Starting production server...");
3068
+ try {
3069
+ const proc = Bun.spawn(["bun", "run", entryPoint], {
3070
+ cwd: projectRoot,
3071
+ env: { ...process.env, ...env },
3072
+ stdout: "inherit",
3073
+ stderr: "inherit"
3074
+ });
3075
+ s.success(`Production server running at ${colors.cyan(`http://${host}:${port}`)}`);
3076
+ const exitCode = await proc.exited;
3077
+ if (exitCode !== 0 && exitCode !== null) {
3078
+ cliConsole.error(`Server exited with code ${exitCode}`);
3079
+ process.exit(exitCode);
3080
+ }
3081
+ } catch (error) {
3082
+ s.error();
3083
+ throw error;
3084
+ }
3085
+ }
3086
+ defineCommand({
3087
+ name: "start",
3088
+ description: "Start the production server",
3089
+ options: [
3090
+ {
3091
+ name: "port",
3092
+ alias: "p",
3093
+ type: "number",
3094
+ default: 3000,
3095
+ description: "Server port"
3096
+ },
3097
+ {
3098
+ name: "host",
3099
+ alias: "H",
3100
+ type: "string",
3101
+ default: "0.0.0.0",
3102
+ description: "Server hostname"
3103
+ },
3104
+ {
3105
+ name: "workers",
3106
+ alias: "w",
3107
+ type: "string",
3108
+ default: "auto",
3109
+ description: "Number of worker threads"
3110
+ },
3111
+ {
3112
+ name: "config",
3113
+ alias: "c",
3114
+ type: "string",
3115
+ description: "Path to config file"
3116
+ }
3117
+ ],
3118
+ examples: [
3119
+ "bueno start",
3120
+ "bueno start --port 8080",
3121
+ "bueno start --host 0.0.0.0",
3122
+ "bueno start --workers 4"
3123
+ ]
3124
+ }, handleStart);
3125
+
3126
+ // src/cli/commands/help.ts
3127
+ defineCommand({
3128
+ name: "help",
3129
+ description: "Show help information for commands",
3130
+ positionals: [
3131
+ {
3132
+ name: "command",
3133
+ required: false,
3134
+ description: "Command to show help for"
3135
+ }
3136
+ ],
3137
+ options: [
3138
+ {
3139
+ name: "all",
3140
+ alias: "a",
3141
+ type: "boolean",
3142
+ default: false,
3143
+ description: "Show help for all commands"
3144
+ }
3145
+ ]
3146
+ }, async (args) => {
3147
+ const commandName = args.positionals[0];
3148
+ if (commandName && registry.has(commandName)) {
3149
+ const cmd = registry.get(commandName);
3150
+ if (cmd) {
3151
+ cliConsole.log(generateHelpText(cmd.definition));
3152
+ }
3153
+ } else if (hasFlag(args, "all")) {
3154
+ cliConsole.log(`
3155
+ Bueno CLI - Available Commands
3156
+ `);
3157
+ for (const cmd of registry.getAll()) {
3158
+ cliConsole.log(generateHelpText(cmd));
3159
+ cliConsole.log("---");
3160
+ }
3161
+ } else {
3162
+ cliConsole.log(generateGlobalHelpText(registry.getAll()));
3163
+ }
3164
+ });
3165
+ // src/cli/index.ts
3166
+ var VERSION = "0.1.0";
3167
+ class CLIError extends Error {
3168
+ type;
3169
+ exitCode;
3170
+ constructor(message, type, exitCode = 1) {
3171
+ super(message);
3172
+ this.type = type;
3173
+ this.exitCode = exitCode;
3174
+ this.name = "CLIError";
3175
+ }
3176
+ }
3177
+ async function run(argv = process.argv.slice(2)) {
3178
+ const args = parseArgs(argv);
3179
+ if (hasFlag(args, "no-color")) {
3180
+ setColorEnabled(false);
3181
+ }
3182
+ if (hasFlag(args, "verbose")) {
3183
+ process.env.BUENO_VERBOSE = "true";
3184
+ }
3185
+ if (hasFlag(args, "quiet")) {
3186
+ process.env.BUENO_QUIET = "true";
3187
+ }
3188
+ if (hasFlag(args, "version") || hasFlag(args, "v")) {
3189
+ cliConsole.log(`bueno v${VERSION}`);
3190
+ process.exit(0);
3191
+ }
3192
+ if (!args.command || hasFlag(args, "help") || hasFlag(args, "h")) {
3193
+ if (args.command && registry.has(args.command)) {
3194
+ const cmd = registry.get(args.command);
3195
+ if (cmd) {
3196
+ cliConsole.log(generateHelpText(cmd.definition));
3197
+ }
3198
+ } else {
3199
+ cliConsole.log(generateGlobalHelpText(registry.getAll()));
3200
+ }
3201
+ process.exit(0);
3202
+ }
3203
+ try {
3204
+ await registry.execute(args.command, args);
3205
+ } catch (error) {
3206
+ if (error instanceof CLIError) {
3207
+ cliConsole.error(error.message);
3208
+ process.exit(error.exitCode);
3209
+ }
3210
+ if (error instanceof Error) {
3211
+ if (process.env.BUENO_VERBOSE === "true") {
3212
+ cliConsole.error(error.stack ?? error.message);
3213
+ } else {
3214
+ cliConsole.error(error.message);
3215
+ }
3216
+ }
3217
+ process.exit(1);
3218
+ }
3219
+ }
3220
+ async function main() {
3221
+ process.on("SIGINT", () => {
3222
+ cliConsole.newline();
3223
+ process.exit(130);
3224
+ });
3225
+ process.on("unhandledRejection", (reason) => {
3226
+ cliConsole.error("Unhandled rejection:", reason);
3227
+ process.exit(1);
3228
+ });
3229
+ await run();
3230
+ }
3231
+
3232
+ // src/cli/bin.ts
3233
+ main();