@buenojs/bueno 0.8.3 → 0.8.5

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