@buenojs/bueno 0.8.2 → 0.8.4

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