@buenojs/bueno 0.8.3 → 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
@@ -1,1300 +1,2927 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
+ var __defProp = Object.defineProperty;
4
+ var __export = (target, all) => {
5
+ for (var name in all)
6
+ __defProp(target, name, {
7
+ get: all[name],
8
+ enumerable: true,
9
+ configurable: true,
10
+ set: (newValue) => all[name] = () => newValue
11
+ });
12
+ };
13
+ var __legacyDecorateClassTS = function(decorators, target, key, desc) {
14
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
15
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
16
+ r = Reflect.decorate(decorators, target, key, desc);
17
+ else
18
+ for (var i = decorators.length - 1;i >= 0; i--)
19
+ if (d = decorators[i])
20
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
21
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
22
+ };
23
+ var __legacyMetadataTS = (k, v) => {
24
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
25
+ return Reflect.metadata(k, v);
26
+ };
27
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
3
28
  var __require = import.meta.require;
4
29
 
5
- // src/cli/index.ts
30
+ // src/cli/utils/version.ts
6
31
  import { readFileSync as readFileSync2 } from "fs";
7
32
  import { join as join2 } from "path";
8
-
9
- // src/cli/core/args.ts
10
- function parseArgs(argv = process.argv.slice(2)) {
11
- const result = {
12
- command: "",
13
- positionals: [],
14
- options: {},
15
- flags: new Set
16
- };
17
- for (let i = 0;i < argv.length; i++) {
18
- const arg = argv[i];
19
- if (!arg)
20
- continue;
21
- if (arg.startsWith("--")) {
22
- const eqIndex = arg.indexOf("=");
23
- if (eqIndex !== -1) {
24
- const name = arg.slice(2, eqIndex);
25
- const value = arg.slice(eqIndex + 1);
26
- result.options[name] = value;
27
- } else {
28
- const name = arg.slice(2);
29
- const nextArg = argv[i + 1];
30
- if (!nextArg || nextArg.startsWith("-")) {
31
- result.options[name] = true;
32
- result.flags.add(name);
33
- } else {
34
- result.options[name] = nextArg;
35
- i++;
36
- }
37
- }
38
- } else if (arg.startsWith("-") && arg.length > 1) {
39
- const chars = arg.slice(1);
40
- if (chars.length > 1) {
41
- for (const char of chars) {
42
- result.options[char] = true;
43
- result.flags.add(char);
44
- }
45
- } else {
46
- const name = chars;
47
- const nextArg = argv[i + 1];
48
- if (!nextArg || nextArg.startsWith("-")) {
49
- result.options[name] = true;
50
- result.flags.add(name);
51
- } else {
52
- result.options[name] = nextArg;
53
- i++;
54
- }
55
- }
56
- } else {
57
- if (!result.command) {
58
- result.command = arg;
59
- } else {
60
- result.positionals.push(arg);
61
- }
62
- }
63
- }
64
- return result;
65
- }
66
- function getOption(parsed, name, definition) {
67
- const value = parsed.options[name] ?? parsed.options[definition.alias ?? ""];
68
- if (value === undefined) {
69
- return definition.default;
70
- }
71
- if (definition.type === "boolean") {
72
- return value === true || value === "true";
73
- }
74
- if (definition.type === "number") {
75
- return typeof value === "number" ? value : typeof value === "string" ? parseInt(value, 10) : NaN;
33
+ function getBuenoVersion() {
34
+ if (cachedVersion) {
35
+ return cachedVersion;
76
36
  }
77
- return value;
78
- }
79
- function hasFlag(parsed, name, alias) {
80
- return parsed.flags.has(name) || (alias ? parsed.flags.has(alias) : false);
81
- }
82
- function getOptionValues(parsed, name, alias) {
83
- const values = [];
84
- const argv = process.argv.slice(2);
85
- for (let i = 0;i < argv.length; i++) {
86
- const arg = argv[i];
87
- if (!arg)
88
- continue;
89
- if (arg === `--${name}`) {
90
- const nextArg = argv[i + 1];
91
- if (nextArg && !nextArg.startsWith("-")) {
92
- values.push(nextArg);
93
- i++;
94
- }
95
- } else if (arg.startsWith(`--${name}=`)) {
96
- const value = arg.slice(name.length + 3);
97
- values.push(value);
98
- } else if (alias && arg === `-${alias}`) {
99
- const nextArg = argv[i + 1];
100
- if (nextArg && !nextArg.startsWith("-")) {
101
- values.push(nextArg);
102
- i++;
103
- }
104
- }
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";
105
45
  }
106
- return values;
107
46
  }
108
- function generateHelpText(command, cliName = "bueno") {
109
- const lines = [];
110
- lines.push(`
111
- ${command.description}
112
- `);
113
- lines.push("Usage:");
114
- let usage = ` ${cliName} ${command.name}`;
115
- if (command.positionals) {
116
- for (const pos of command.positionals) {
117
- usage += pos.required ? ` <${pos.name}>` : ` [${pos.name}]`;
118
- }
119
- }
120
- usage += " [options]";
121
- lines.push(usage + `
122
- `);
123
- if (command.positionals && command.positionals.length > 0) {
124
- lines.push("Arguments:");
125
- for (const pos of command.positionals) {
126
- const required = pos.required ? " (required)" : "";
127
- lines.push(` ${pos.name.padEnd(20)} ${pos.description}${required}`);
128
- }
129
- lines.push("");
130
- }
131
- if (command.options && command.options.length > 0) {
132
- lines.push("Options:");
133
- for (const opt of command.options) {
134
- let flag = `--${opt.name}`;
135
- if (opt.alias) {
136
- flag = `-${opt.alias}, ${flag}`;
137
- }
138
- let defaultValue = "";
139
- if (opt.default !== undefined) {
140
- defaultValue = ` (default: ${opt.default})`;
141
- }
142
- lines.push(` ${flag.padEnd(20)} ${opt.description}${defaultValue}`);
143
- }
144
- lines.push("");
145
- }
146
- if (command.examples && command.examples.length > 0) {
147
- lines.push("Examples:");
148
- for (const example of command.examples) {
149
- lines.push(` ${example}`);
150
- }
151
- lines.push("");
152
- }
153
- return lines.join(`
154
- `);
47
+ function getBuenoDependency() {
48
+ return {
49
+ "@buenojs/bueno": getBuenoVersion()
50
+ };
155
51
  }
156
- function generateGlobalHelpText(commands, cliName = "bueno") {
157
- const lines = [];
158
- lines.push(`
159
- ${cliName} - A Bun-Native Full-Stack Framework CLI
160
- `);
161
- lines.push("Usage:");
162
- lines.push(` ${cliName} <command> [options]
163
- `);
164
- lines.push("Commands:");
165
- for (const cmd of commands) {
166
- const name = cmd.alias ? `${cmd.name} (${cmd.alias})` : cmd.name;
167
- lines.push(` ${name.padEnd(20)} ${cmd.description}`);
168
- }
169
- lines.push("");
170
- lines.push("Global Options:");
171
- lines.push(" --help, -h Show help for command");
172
- lines.push(" --version, -v Show CLI version");
173
- lines.push(" --verbose Enable verbose output");
174
- lines.push(" --quiet Suppress non-essential output");
175
- lines.push(" --no-color Disable colored output");
176
- lines.push("");
177
- lines.push(`Run '${cliName} <command> --help' for more information about a command.
178
- `);
179
- return lines.join(`
180
- `);
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
+ `;
181
125
  }
126
+ function getDockerignoreTemplate() {
127
+ return `# Dependencies
128
+ node_modules/
182
129
 
183
- // src/cli/core/console.ts
184
- var COLORS = {
185
- reset: "\x1B[0m",
186
- bold: "\x1B[1m",
187
- dim: "\x1B[2m",
188
- italic: "\x1B[3m",
189
- underline: "\x1B[4m",
190
- black: "\x1B[30m",
191
- red: "\x1B[31m",
192
- green: "\x1B[32m",
193
- yellow: "\x1B[33m",
194
- blue: "\x1B[34m",
195
- magenta: "\x1B[35m",
196
- cyan: "\x1B[36m",
197
- white: "\x1B[37m",
198
- brightRed: "\x1B[91m",
199
- brightGreen: "\x1B[92m",
200
- brightYellow: "\x1B[93m",
201
- brightBlue: "\x1B[94m",
202
- brightMagenta: "\x1B[95m",
203
- brightCyan: "\x1B[96m",
204
- brightWhite: "\x1B[97m",
205
- bgBlack: "\x1B[40m",
206
- bgRed: "\x1B[41m",
207
- bgGreen: "\x1B[42m",
208
- bgYellow: "\x1B[43m",
209
- bgBlue: "\x1B[44m",
210
- bgMagenta: "\x1B[45m",
211
- bgCyan: "\x1B[46m",
212
- bgWhite: "\x1B[47m"
213
- };
214
- var colorEnabled = !process.env.NO_COLOR && process.env.BUENO_NO_COLOR !== "true" && process.stdout.isTTY;
215
- function setColorEnabled(enabled) {
216
- colorEnabled = enabled;
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
+ };
217
1346
  }
218
- function isColorEnabled() {
219
- 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
+ }
220
1364
  }
221
- function colorize(text, color) {
222
- if (!colorEnabled)
223
- return text;
224
- 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
+ }
225
1380
  }
226
- var colors = {
227
- red: (text) => colorize(text, "red"),
228
- green: (text) => colorize(text, "green"),
229
- yellow: (text) => colorize(text, "yellow"),
230
- blue: (text) => colorize(text, "blue"),
231
- magenta: (text) => colorize(text, "magenta"),
232
- cyan: (text) => colorize(text, "cyan"),
233
- white: (text) => colorize(text, "white"),
234
- brightRed: (text) => colorize(text, "brightRed"),
235
- brightGreen: (text) => colorize(text, "brightGreen"),
236
- brightYellow: (text) => colorize(text, "brightYellow"),
237
- brightBlue: (text) => colorize(text, "brightBlue"),
238
- brightCyan: (text) => colorize(text, "brightCyan"),
239
- dim: (text) => colorize(text, "dim"),
240
- bold: (text) => colorize(text, "bold"),
241
- underline: (text) => colorize(text, "underline"),
242
- italic: (text) => colorize(text, "italic")
243
- };
244
- var cliConsole = {
245
- log(message, ...args) {
246
- globalThis.console.log(message, ...args);
247
- },
248
- info(message, ...args) {
249
- globalThis.console.log(colors.cyan("\u2139"), message, ...args);
250
- },
251
- success(message, ...args) {
252
- globalThis.console.log(colors.green("\u2713"), message, ...args);
253
- },
254
- warn(message, ...args) {
255
- globalThis.console.log(colors.yellow("\u26A0"), message, ...args);
256
- },
257
- error(message, ...args) {
258
- globalThis.console.error(colors.red("\u2717"), message, ...args);
259
- },
260
- debug(message, ...args) {
261
- if (process.env.BUENO_VERBOSE === "true") {
262
- globalThis.console.log(colors.dim("\u22EF"), colors.dim(message), ...args);
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"
263
1420
  }
264
- },
265
- header(title) {
266
- globalThis.console.log();
267
- globalThis.console.log(colors.bold(colors.cyan(title)));
268
- globalThis.console.log();
269
- },
270
- subheader(title) {
271
- globalThis.console.log();
272
- globalThis.console.log(colors.bold(title));
273
- },
274
- newline() {
275
- globalThis.console.log();
276
- },
277
- clear() {
278
- process.stdout.write("\x1B[2J\x1B[0f");
279
- }
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: '/',
280
1446
  };
281
- function formatTable(headers, rows, options = {}) {
282
- const padding = options.padding ?? 2;
283
- const widths = headers.map((h, i) => {
284
- const maxRowWidth = Math.max(...rows.map((r) => r[i]?.length ?? 0));
285
- return Math.max(h.length, maxRowWidth);
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
+ },
286
1543
  });
287
- const pad = " ".repeat(padding);
288
- const headerLine = headers.map((h, i) => h.padEnd(widths[i] ?? 0)).join(pad);
289
- const separator = widths.map((w) => "\u2500".repeat(w)).join(pad);
290
- const rowLines = rows.map((row) => row.map((cell, i) => (cell ?? "").padEnd(widths[i] ?? 0)).join(pad));
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() {
291
1691
  return [
292
- colors.bold(headerLine),
293
- colors.dim(separator),
294
- ...rowLines
295
- ].join(`
296
- `);
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
+ ];
297
1698
  }
298
- function printTable(headers, rows, options) {
299
- 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
+ };
300
1723
  }
301
- function formatSize(bytes) {
302
- const units = ["B", "KB", "MB", "GB"];
303
- let size = bytes;
304
- let unitIndex = 0;
305
- while (size >= 1024 && unitIndex < units.length - 1) {
306
- size /= 1024;
307
- unitIndex++;
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 "";
308
1846
  }
309
- return `${size.toFixed(1)} ${units[unitIndex]}`;
310
1847
  }
311
- function formatDuration(ms) {
312
- if (ms < 1000)
313
- return `${ms}ms`;
314
- if (ms < 60000)
315
- return `${(ms / 1000).toFixed(1)}s`;
316
- return `${(ms / 60000).toFixed(1)}m`;
1848
+ function getFileExtension(type) {
1849
+ return type === "dto" ? ".dto.ts" : ".ts";
317
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';
318
1854
 
319
- // src/cli/commands/index.ts
320
- class CommandRegistry {
321
- commands = new Map;
322
- aliases = new Map;
323
- register(definition, handler) {
324
- this.commands.set(definition.name, {
325
- definition,
326
- handler
327
- });
328
- if (definition.alias) {
329
- this.aliases.set(definition.alias, definition.name);
330
- }
331
- }
332
- get(name) {
333
- const commandName = this.aliases.get(name) ?? name;
334
- return this.commands.get(commandName);
335
- }
336
- has(name) {
337
- const commandName = this.aliases.get(name) ?? name;
338
- return this.commands.has(commandName);
339
- }
340
- getAll() {
341
- return Array.from(this.commands.values()).map((c) => c.definition);
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' };
342
1864
  }
343
- getCommands() {
344
- 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' };
345
1870
  }
346
- async execute(name, args) {
347
- const command = this.get(name);
348
- if (!command) {
349
- throw new Error(`Unknown command: ${name}`);
350
- }
351
- await command.handler(args);
1871
+
1872
+ @Post()
1873
+ async create(ctx: Context) {
1874
+ const body = await ctx.body();
1875
+ return { message: 'Created', data: body };
352
1876
  }
353
- }
354
- var registry = new CommandRegistry;
355
- function defineCommand(definition, handler) {
356
- registry.register(definition, handler);
357
- }
358
1877
 
359
- // src/cli/core/prompt.ts
360
- import * as readline from "readline";
361
- function isInteractive() {
362
- return !!(process.stdin.isTTY && process.stdout.isTTY);
363
- }
364
- function createRL() {
365
- return readline.createInterface({
366
- input: process.stdin,
367
- output: process.stdout
368
- });
369
- }
370
- async function prompt(message, options = {}) {
371
- const defaultValue = options.default;
372
- const promptText = defaultValue ? `${colors.cyan("?")} ${message} ${colors.dim(`(${defaultValue})`)}: ` : `${colors.cyan("?")} ${message}: `;
373
- if (!isInteractive()) {
374
- return defaultValue ?? "";
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 };
375
1883
  }
376
- return new Promise((resolve) => {
377
- const rl = createRL();
378
- rl.question(promptText, (answer) => {
379
- rl.close();
380
- const value = answer.trim() || defaultValue || "";
381
- if (options.validate) {
382
- const result = options.validate(value);
383
- if (result !== true) {
384
- const errorMsg = typeof result === "string" ? result : "Invalid value";
385
- process.stdout.write(`${colors.red("\u2717")} ${errorMsg}
386
- `);
387
- prompt(message, options).then(resolve);
388
- return;
389
- }
390
- }
391
- resolve(value);
392
- });
393
- });
394
- }
395
- async function confirm(message, options = {}) {
396
- const defaultValue = options.default ?? false;
397
- const hint = defaultValue ? "Y/n" : "y/N";
398
- if (!isInteractive()) {
399
- return defaultValue;
1884
+
1885
+ @Delete(':id')
1886
+ async remove(ctx: Context) {
1887
+ const id = ctx.params.id;
1888
+ return { id, message: 'Deleted' };
400
1889
  }
401
- const answer = await prompt(`${message} ${colors.dim(`(${hint})`)}`, {
402
- default: defaultValue ? "y" : "n",
403
- validate: (value) => {
404
- if (!value)
405
- return true;
406
- return ["y", "yes", "n", "no"].includes(value.toLowerCase()) || "Please enter y or n";
407
- }
408
- });
409
- return ["y", "yes"].includes(answer.toLowerCase());
410
1890
  }
411
- async function select(message, choices, options = {}) {
412
- if (!isInteractive()) {
413
- return options.default ?? choices[0]?.value;
414
- }
415
- const pageSize = options.pageSize ?? 10;
416
- let selectedIndex = choices.findIndex((c) => c.value === options.default && !c.disabled);
417
- if (selectedIndex === -1) {
418
- selectedIndex = choices.findIndex((c) => !c.disabled);
419
- }
420
- return new Promise((resolve) => {
421
- process.stdout.write("\x1B[?25l");
422
- const render = () => {
423
- const lines = Math.min(choices.length, pageSize);
424
- process.stdout.write(`\x1B[${lines + 1}A\x1B[0J`);
425
- process.stdout.write(`${colors.cyan("?")} ${message}
426
- `);
427
- const start = Math.max(0, selectedIndex - pageSize + 1);
428
- const end = Math.min(choices.length, start + pageSize);
429
- for (let i = start;i < end; i++) {
430
- const choice = choices[i];
431
- if (!choice)
432
- continue;
433
- const isSelected = i === selectedIndex;
434
- const prefix = isSelected ? `${colors.cyan("\u276F")} ` : " ";
435
- const name = choice.name ?? choice.value;
436
- const text = choice.disabled ? colors.dim(`${name} (disabled)`) : isSelected ? colors.cyan(name) : name;
437
- process.stdout.write(`${prefix}${text}
438
- `);
439
- }
440
- };
441
- process.stdout.write(`${colors.cyan("?")} ${message}
442
- `);
443
- render();
444
- const stdin = process.stdin;
445
- stdin.setRawMode(true);
446
- stdin.resume();
447
- stdin.setEncoding("utf8");
448
- const cleanup = () => {
449
- stdin.setRawMode(false);
450
- stdin.pause();
451
- stdin.removeListener("data", handler);
452
- process.stdout.write("\x1B[?25h");
453
- };
454
- const handler = (key) => {
455
- if (key === "\x1B[A" || key === "k") {
456
- do {
457
- selectedIndex = (selectedIndex - 1 + choices.length) % choices.length;
458
- } while (choices[selectedIndex]?.disabled);
459
- render();
460
- } else if (key === "\x1B[B" || key === "j") {
461
- do {
462
- selectedIndex = (selectedIndex + 1) % choices.length;
463
- } while (choices[selectedIndex]?.disabled);
464
- render();
465
- } else if (key === "\r" || key === `
466
- `) {
467
- cleanup();
468
- const selected = choices[selectedIndex];
469
- if (selected) {
470
- process.stdout.write(`\x1B[${Math.min(choices.length, pageSize) + 1}A\x1B[0J`);
471
- process.stdout.write(`${colors.cyan("?")} ${message} ${colors.cyan(selected.name ?? selected.value)}
472
- `);
473
- resolve(selected.value);
474
- }
475
- } else if (key === "\x1B" || key === "\x03") {
476
- cleanup();
477
- process.stdout.write(`
478
- `);
479
- process.exit(130);
480
- }
481
- };
482
- stdin.on("data", handler);
483
- });
1891
+ `;
484
1892
  }
1893
+ function getServiceTemplate() {
1894
+ return `import { Injectable } from '@buenojs/bueno';
485
1895
 
486
- // src/cli/core/spinner.ts
487
- var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
488
- var SPINNER_INTERVAL = 80;
489
-
490
- class Spinner {
491
- text;
492
- color;
493
- frameIndex = 0;
494
- interval = null;
495
- isSpinning = false;
496
- stream = process.stdout;
497
- constructor(options = {}) {
498
- this.text = options.text ?? "";
499
- this.color = options.color ?? "cyan";
1896
+ @Injectable()
1897
+ export class {{pascalCase name}}Service {
1898
+ async findAll() {
1899
+ // TODO: Implement findAll
1900
+ return [];
500
1901
  }
501
- start(text) {
502
- if (text)
503
- this.text = text;
504
- if (this.isSpinning)
505
- return this;
506
- this.isSpinning = true;
507
- this.frameIndex = 0;
508
- this.stream.write("\x1B[?25l");
509
- this.interval = setInterval(() => {
510
- this.render();
511
- }, SPINNER_INTERVAL);
512
- return this;
1902
+
1903
+ async findOne(id: string) {
1904
+ // TODO: Implement findOne
1905
+ return { id };
513
1906
  }
514
- update(text) {
515
- this.text = text;
516
- if (this.isSpinning) {
517
- this.render();
518
- }
519
- return this;
1907
+
1908
+ async create(data: unknown) {
1909
+ // TODO: Implement create
1910
+ return data;
520
1911
  }
521
- success(text) {
522
- return this.stop(colors.green("\u2713"), text);
1912
+
1913
+ async update(id: string, data: unknown) {
1914
+ // TODO: Implement update
1915
+ return { id, ...data };
523
1916
  }
524
- error(text) {
525
- return this.stop(colors.red("\u2717"), text);
1917
+
1918
+ async remove(id: string) {
1919
+ // TODO: Implement remove
1920
+ return { id };
526
1921
  }
527
- warn(text) {
528
- 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;
529
1947
  }
530
- info(text) {
531
- 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;
532
1968
  }
533
- stop(symbol, text) {
534
- if (!this.isSpinning)
535
- return this;
536
- this.isSpinning = false;
537
- if (this.interval) {
538
- clearInterval(this.interval);
539
- this.interval = null;
540
- }
541
- this.stream.write("\r\x1B[K");
542
- const finalText = text ?? this.text;
543
- if (symbol) {
544
- this.stream.write(`${symbol} ${finalText}
545
- `);
546
- } else {
547
- this.stream.write(`${finalText}
548
- `);
549
- }
550
- this.stream.write("\x1B[?25h");
551
- return this;
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;
552
1981
  }
553
- clear() {
554
- if (!this.isSpinning)
555
- return this;
556
- this.stream.write("\r\x1B[K");
557
- 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
+ );
558
2006
  }
559
- render() {
560
- if (!isColorEnabled()) {
561
- const dots = ".".repeat(this.frameIndex % 3 + 1);
562
- this.stream.write(`\r${this.text}${dots} `);
563
- this.frameIndex++;
564
- return;
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
+ }
565
2198
  }
566
- const frame = SPINNER_FRAMES[this.frameIndex % SPINNER_FRAMES.length];
567
- const coloredFrame = colors[this.color](frame);
568
- this.stream.write(`\r${coloredFrame} ${this.text}`);
569
- this.frameIndex++;
570
2199
  }
2200
+ return result;
571
2201
  }
572
- function spinner(text, options) {
573
- return new Spinner({ text, ...options }).start();
574
- }
575
-
576
- class ProgressBar {
577
- total;
578
- width;
579
- text;
580
- completeChar;
581
- incompleteChar;
582
- current = 0;
583
- stream = process.stdout;
584
- constructor(options) {
585
- this.total = options.total;
586
- this.width = options.width ?? 40;
587
- this.text = options.text ?? "";
588
- this.completeChar = options.completeChar ?? "\u2588";
589
- this.incompleteChar = options.incompleteChar ?? "\u2591";
590
- }
591
- start() {
592
- this.current = 0;
593
- this.render();
594
- return this;
595
- }
596
- update(current) {
597
- this.current = Math.min(current, this.total);
598
- this.render();
599
- return this;
600
- }
601
- increment(amount = 1) {
602
- return this.update(this.current + amount);
603
- }
604
- complete() {
605
- this.current = this.total;
606
- this.render();
607
- this.stream.write(`
608
- `);
609
- return this;
610
- }
611
- render() {
612
- const percent = this.current / this.total;
613
- const completeWidth = Math.round(this.width * percent);
614
- const incompleteWidth = this.width - completeWidth;
615
- const complete = this.completeChar.repeat(completeWidth);
616
- const incomplete = this.incompleteChar.repeat(incompleteWidth);
617
- const bar = colors.green(complete) + colors.dim(incomplete);
618
- const percentText = `${Math.round(percent * 100)}%`.padStart(4);
619
- const line = `\r${this.text} [${bar}] ${percentText} ${this.current}/${this.total}`;
620
- this.stream.write(`\r\x1B[K${line}`);
2202
+ function getOption(parsed, name, definition) {
2203
+ const value = parsed.options[name] ?? parsed.options[definition.alias ?? ""];
2204
+ if (value === undefined) {
2205
+ return definition.default;
621
2206
  }
622
- }
623
- async function runTasks(tasks) {
624
- for (const task of tasks) {
625
- const s = spinner(task.text);
626
- try {
627
- await task.task();
628
- s.success();
629
- } catch (error) {
630
- s.error();
631
- throw error;
632
- }
2207
+ if (definition.type === "boolean") {
2208
+ return value === true || value === "true";
633
2209
  }
634
- }
635
-
636
- // src/cli/utils/fs.ts
637
- import * as fs from "fs";
638
- import * as path from "path";
639
- async function fileExists(filePath) {
640
- try {
641
- return await Bun.file(filePath).exists();
642
- } catch {
643
- return false;
2210
+ if (definition.type === "number") {
2211
+ return typeof value === "number" ? value : typeof value === "string" ? parseInt(value, 10) : NaN;
644
2212
  }
2213
+ return value;
645
2214
  }
646
- async function createDirectory(dirPath) {
647
- await fs.promises.mkdir(dirPath, { recursive: true });
648
- }
649
- async function readFile(filePath) {
650
- return await Bun.file(filePath).text();
651
- }
652
- async function writeFile(filePath, content) {
653
- const dir = path.dirname(filePath);
654
- await createDirectory(dir);
655
- await Bun.write(filePath, content);
656
- }
657
- async function deleteDirectory(dirPath) {
658
- 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);
659
2217
  }
660
- async function listFiles(dirPath, options = {}) {
661
- const files = [];
662
- async function walk(dir) {
663
- const entries = await fs.promises.readdir(dir, { withFileTypes: true });
664
- for (const entry of entries) {
665
- const fullPath = path.join(dir, entry.name);
666
- if (entry.isDirectory() && options.recursive) {
667
- await walk(fullPath);
668
- } else if (entry.isFile()) {
669
- if (!options.pattern || options.pattern.test(entry.name)) {
670
- files.push(fullPath);
671
- }
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++;
672
2239
  }
673
2240
  }
674
2241
  }
675
- await walk(dirPath);
676
- return files;
2242
+ return values;
677
2243
  }
678
- async function findFileUp(startDir, fileName, options = {}) {
679
- let currentDir = startDir;
680
- const stopAt = options.stopAt ?? "/";
681
- while (currentDir !== stopAt && currentDir !== "/") {
682
- const filePath = path.join(currentDir, fileName);
683
- if (await fileExists(filePath)) {
684
- return filePath;
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}]`;
685
2254
  }
686
- currentDir = path.dirname(currentDir);
687
- }
688
- return null;
689
- }
690
- async function getProjectRoot(startDir = process.cwd()) {
691
- const packageJsonPath = await findFileUp(startDir, "package.json");
692
- if (packageJsonPath) {
693
- return path.dirname(packageJsonPath);
694
2255
  }
695
- return null;
696
- }
697
- async function isBuenoProject(dir = process.cwd()) {
698
- const root = await getProjectRoot(dir);
699
- if (!root)
700
- return false;
701
- const configPath = path.join(root, "bueno.config.ts");
702
- if (await fileExists(configPath))
703
- return true;
704
- const packageJsonPath = path.join(root, "package.json");
705
- if (await fileExists(packageJsonPath)) {
706
- const content = await readFile(packageJsonPath);
707
- try {
708
- const pkg = JSON.parse(content);
709
- return !!(pkg.dependencies?.bueno || pkg.devDependencies?.bueno);
710
- } catch {
711
- return false;
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}`);
712
2264
  }
2265
+ lines.push("");
713
2266
  }
714
- return false;
715
- }
716
- function joinPaths(...paths) {
717
- return path.join(...paths);
718
- }
719
- function processTemplate(template, data) {
720
- let result = template;
721
- result = result.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (_, key, content) => {
722
- const value = data[key];
723
- return value ? content : "";
724
- });
725
- result = result.replace(/\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g, (_, key, content) => {
726
- const items = data[key];
727
- if (!Array.isArray(items))
728
- return "";
729
- return items.map((item) => {
730
- let itemContent = content;
731
- if (typeof item === "object" && item !== null) {
732
- for (const [k, v] of Object.entries(item)) {
733
- itemContent = itemContent.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), String(v));
734
- }
735
- }
736
- return itemContent;
737
- }).join("");
738
- });
739
- const helpers = {
740
- camelCase: (v) => v.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toLowerCase()),
741
- pascalCase: (v) => v.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toUpperCase()),
742
- kebabCase: (v) => v.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[-_\s]+/g, "-").toLowerCase(),
743
- snakeCase: (v) => v.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase(),
744
- upperCase: (v) => v.toUpperCase(),
745
- lowerCase: (v) => v.toLowerCase(),
746
- capitalize: (v) => v.charAt(0).toUpperCase() + v.slice(1),
747
- pluralize: (v) => {
748
- if (v.endsWith("y") && !["ay", "ey", "iy", "oy", "uy"].some((e) => v.endsWith(e))) {
749
- return v.slice(0, -1) + "ies";
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}`;
750
2273
  }
751
- if (v.endsWith("s") || v.endsWith("x") || v.endsWith("z") || v.endsWith("ch") || v.endsWith("sh")) {
752
- return v + "es";
2274
+ let defaultValue = "";
2275
+ if (opt.default !== undefined) {
2276
+ defaultValue = ` (default: ${opt.default})`;
753
2277
  }
754
- return v + "s";
2278
+ lines.push(` ${flag.padEnd(20)} ${opt.description}${defaultValue}`);
755
2279
  }
756
- };
757
- for (const [helperName, helperFn] of Object.entries(helpers)) {
758
- const regex = new RegExp(`\\{\\{${helperName}\\s+(\\w+)\\}\\}`, "g");
759
- result = result.replace(regex, (_, key) => {
760
- const value = data[key];
761
- if (typeof value === "string") {
762
- return helperFn(value);
763
- }
764
- return String(value);
765
- });
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("");
766
2288
  }
767
- for (const [key, value] of Object.entries(data)) {
768
- const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
769
- 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}`);
770
2304
  }
771
- 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.
772
2314
  `);
773
- result = result.replace(/\n{3,}/g, `
774
-
2315
+ return lines.join(`
775
2316
  `);
776
- return result.trim();
777
2317
  }
778
2318
 
779
- // src/cli/utils/strings.ts
780
- function kebabCase(str) {
781
- return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[-_\s]+/g, "-").toLowerCase();
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;
782
2353
  }
783
-
784
- // src/cli/templates/docker.ts
785
- function getDockerfileTemplate(projectName, database) {
786
- return `# ${projectName} - Production Dockerfile
787
- # Multi-stage build for optimized production image
788
-
789
- # Stage 1: Install dependencies
790
- FROM oven/bun:1 AS deps
791
-
792
- WORKDIR /app
793
-
794
- # Copy package files first for better layer caching
795
- COPY package.json bun.lock* ./
796
-
797
- # Install dependencies
798
- RUN bun install --frozen-lockfile --production
799
-
800
- # Stage 2: Build the application
801
- FROM oven/bun:1 AS builder
802
-
803
- WORKDIR /app
804
-
805
- # Copy package files
806
- COPY package.json bun.lock* ./
807
-
808
- # Install all dependencies (including devDependencies for build)
809
- RUN bun install --frozen-lockfile
810
-
811
- # Copy source code
812
- COPY . .
813
-
814
- # Build the application
815
- RUN bun run build
816
-
817
- # Stage 3: Production image
818
- FROM oven/bun:1 AS runner
819
-
820
- WORKDIR /app
821
-
822
- # Set production environment
823
- ENV NODE_ENV=production
824
- ENV BUN_ENV=production
825
-
826
- # Create non-root user for security
827
- RUN addgroup --system --gid 1001 bunjs \\
828
- && adduser --system --uid 1001 --ingroup bunjs bunuser
829
-
830
- # Copy built application from builder
831
- COPY --from=builder /app/dist ./dist
832
- COPY --from=builder /app/node_modules ./node_modules
833
- COPY --from=builder /app/package.json ./
834
-
835
- # Copy config files if they exist
836
- COPY --from=builder /app/bueno.config.ts* ./
837
-
838
- # Set proper ownership
839
- RUN chown -R bunuser:bunjs /app
840
-
841
- # Switch to non-root user
842
- USER bunuser
843
-
844
- # Expose the application port
845
- EXPOSE 3000
846
-
847
- # Health check
848
- HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\
849
- CMD curl -f http://localhost:3000/health || exit 1
850
-
851
- # Start the application
852
- CMD ["bun", "run", "dist/main.js"]
853
- `;
2354
+ function isColorEnabled() {
2355
+ return colorEnabled;
854
2356
  }
855
- function getDockerignoreTemplate() {
856
- return `# Dependencies
857
- node_modules/
858
-
859
- # Build output
860
- dist/
861
-
862
- # Environment files
863
- .env
864
- .env.local
865
- .env.*.local
866
-
867
- # IDE
868
- .idea/
869
- .vscode/
870
- *.swp
871
- *.swo
872
-
873
- # OS
874
- .DS_Store
875
- Thumbs.db
876
-
877
- # Git
878
- .git/
879
- .gitignore
880
-
881
- # Docker
882
- Dockerfile
883
- docker-compose*.yml
884
- .dockerignore
885
-
886
- # Test files
887
- tests/
888
- coverage/
889
- *.test.ts
890
- *.spec.ts
891
-
892
- # Documentation
893
- *.md
894
- !README.md
895
-
896
- # Database files (local)
897
- *.db
898
- *.sqlite
899
- *.sqlite3
900
-
901
- # Logs
902
- *.log
903
- logs/
904
-
905
- # Misc
906
- .editorconfig
907
- .eslintrc*
908
- .prettierrc*
909
- tsconfig.json
910
- `;
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`;
911
2453
  }
912
- function getDockerComposeTemplate(projectName, database) {
913
- const kebabName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
914
- let databaseServices = "";
915
- let dependsOn = "";
916
- if (database === "postgresql") {
917
- databaseServices = `
918
- # PostgreSQL Database
919
- postgres:
920
- image: postgres:16-alpine
921
- container_name: ${kebabName}-postgres
922
- restart: unless-stopped
923
- environment:
924
- POSTGRES_USER: \${POSTGRES_USER:-postgres}
925
- POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-postgres}
926
- POSTGRES_DB: \${POSTGRES_DB:-${kebabName}}
927
- volumes:
928
- - postgres_data:/var/lib/postgresql/data
929
- ports:
930
- - "\${POSTGRES_PORT:-5432}:5432"
931
- healthcheck:
932
- test: ["CMD-SHELL", "pg_isready -U \${POSTGRES_USER:-postgres} -d \${POSTGRES_DB:-${kebabName}}"]
933
- interval: 10s
934
- timeout: 5s
935
- retries: 5
936
- networks:
937
- - bueno-network
938
-
939
- `;
940
- dependsOn = `
941
- depends_on:
942
- postgres:
943
- condition: service_healthy
944
- `;
945
- } else if (database === "mysql") {
946
- databaseServices = `
947
- # MySQL Database
948
- mysql:
949
- image: mysql:8.0
950
- container_name: ${kebabName}-mysql
951
- restart: unless-stopped
952
- environment:
953
- MYSQL_ROOT_PASSWORD: \${MYSQL_ROOT_PASSWORD:-root}
954
- MYSQL_USER: \${MYSQL_USER:-mysql}
955
- MYSQL_PASSWORD: \${MYSQL_PASSWORD:-mysql}
956
- MYSQL_DATABASE: \${MYSQL_DATABASE:-${kebabName}}
957
- volumes:
958
- - mysql_data:/var/lib/mysql
959
- ports:
960
- - "\${MYSQL_PORT:-3306}:3306"
961
- healthcheck:
962
- test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p\${MYSQL_ROOT_PASSWORD:-root}"]
963
- interval: 10s
964
- timeout: 5s
965
- retries: 5
966
- networks:
967
- - bueno-network
968
2454
 
969
- `;
970
- dependsOn = `
971
- depends_on:
972
- mysql:
973
- condition: service_healthy
974
- `;
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
+ }
975
2467
  }
976
- const volumes = database === "postgresql" ? `
977
- volumes:
978
- postgres_data:
979
- driver: local
980
- ` : database === "mysql" ? `
981
- volumes:
982
- mysql_data:
983
- driver: local
984
- ` : "";
985
- const databaseEnv = database === "postgresql" ? ` DATABASE_URL: postgresql://\${POSTGRES_USER:-postgres}:\${POSTGRES_PASSWORD:-postgres}@postgres:5432/\${POSTGRES_DB:-${kebabName}}
986
- ` : database === "mysql" ? ` DATABASE_URL: mysql://\${MYSQL_USER:-mysql}:\${MYSQL_PASSWORD:-mysql}@mysql:3306/\${MYSQL_DATABASE:-${kebabName}}
987
- ` : "";
988
- return `# ${projectName} - Docker Compose for Local Development
989
- # Usage: docker-compose up -d
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
+ }
990
2494
 
991
- services:
992
- # Application Service
993
- app:
994
- build:
995
- context: .
996
- dockerfile: Dockerfile
997
- container_name: ${kebabName}-app
998
- restart: unless-stopped
999
- ports:
1000
- - "\${APP_PORT:-3000}:3000"
1001
- environment:
1002
- NODE_ENV: production
1003
- BUN_ENV: production
1004
- ${databaseEnv}${dependsOn} networks:
1005
- - bueno-network
1006
- healthcheck:
1007
- test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
1008
- interval: 30s
1009
- timeout: 10s
1010
- retries: 3
1011
- start_period: 10s
1012
- ${databaseServices}networks:
1013
- bueno-network:
1014
- driver: bridge
1015
- ${volumes}
1016
- `;
2495
+ // src/cli/core/prompt.ts
2496
+ import * as readline from "readline";
2497
+ function isInteractive() {
2498
+ return !!(process.stdin.isTTY && process.stdout.isTTY);
1017
2499
  }
1018
- function getDockerEnvTemplate(projectName, database) {
1019
- const kebabName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
1020
- let dbEnv = "";
1021
- if (database === "postgresql") {
1022
- dbEnv = `
1023
- # PostgreSQL Configuration
1024
- POSTGRES_USER=postgres
1025
- POSTGRES_PASSWORD=postgres
1026
- POSTGRES_DB=${kebabName}
1027
- POSTGRES_PORT=5432
1028
- DATABASE_URL=postgresql://postgres:postgres@localhost:5432/${kebabName}
1029
- `;
1030
- } else if (database === "mysql") {
1031
- dbEnv = `
1032
- # MySQL Configuration
1033
- MYSQL_ROOT_PASSWORD=root
1034
- MYSQL_USER=mysql
1035
- MYSQL_PASSWORD=mysql
1036
- MYSQL_DATABASE=${kebabName}
1037
- MYSQL_PORT=3306
1038
- DATABASE_URL=mysql://mysql:mysql@localhost:3306/${kebabName}
1039
- `;
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);
1040
2555
  }
1041
- return `# ${projectName} - Docker Environment Variables
1042
- # Copy this file to .env and update values as needed
1043
-
1044
- # Application
1045
- APP_PORT=3000
1046
- NODE_ENV=production
1047
- ${dbEnv}`;
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
+ });
1048
2620
  }
1049
2621
 
1050
- // src/cli/templates/deploy.ts
1051
- function getRenderYamlTemplate(projectName, database) {
1052
- const kebabName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
1053
- let databaseSection = "";
1054
- let envVars = "";
1055
- if (database === "postgresql") {
1056
- databaseSection = `
1057
- # PostgreSQL Database
1058
- - type: pserv
1059
- name: ${kebabName}-db
1060
- env: docker
1061
- region: oregon
1062
- plan: starter
1063
- envVars:
1064
- - key: POSTGRES_USER
1065
- generateValue: true
1066
- - key: POSTGRES_PASSWORD
1067
- generateValue: true
1068
- - key: POSTGRES_DB
1069
- value: ${kebabName}
1070
- disk:
1071
- name: postgres-data
1072
- mountPath: /var/lib/postgresql/data
1073
- sizeGB: 10
1074
-
1075
- `;
1076
- envVars = `
1077
- envVars:
1078
- - key: DATABASE_URL
1079
- fromDatabase:
1080
- name: ${kebabName}-db
1081
- property: connectionString
1082
- - key: NODE_ENV
1083
- value: production
1084
- - key: BUN_ENV
1085
- value: production
1086
- `;
1087
- } else if (database === "mysql") {
1088
- databaseSection = `
1089
- # MySQL Database (using Render's managed MySQL)
1090
- - type: pserv
1091
- name: ${kebabName}-db
1092
- env: docker
1093
- region: oregon
1094
- plan: starter
1095
- envVars:
1096
- - key: MYSQL_ROOT_PASSWORD
1097
- generateValue: true
1098
- - key: MYSQL_USER
1099
- generateValue: true
1100
- - key: MYSQL_PASSWORD
1101
- generateValue: true
1102
- - key: MYSQL_DATABASE
1103
- value: ${kebabName}
1104
- disk:
1105
- name: mysql-data
1106
- mountPath: /var/lib/mysql
1107
- sizeGB: 10
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;
1108
2625
 
1109
- `;
1110
- envVars = `
1111
- envVars:
1112
- - key: DATABASE_URL
1113
- fromDatabase:
1114
- name: ${kebabName}-db
1115
- property: connectionString
1116
- - key: NODE_ENV
1117
- value: production
1118
- - key: BUN_ENV
1119
- value: production
1120
- `;
1121
- } else {
1122
- envVars = `
1123
- envVars:
1124
- - key: NODE_ENV
1125
- value: production
1126
- - key: BUN_ENV
1127
- value: production
1128
- `;
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++;
1129
2706
  }
1130
- return `# ${projectName} - Render.com Deployment Configuration
1131
- # https://render.com/docs/blueprint-spec
1132
-
1133
- services:
1134
- # Web Service
1135
- - type: web
1136
- name: ${kebabName}
1137
- env: docker
1138
- region: oregon
1139
- plan: starter
1140
- branch: main
1141
- dockerfilePath: ./Dockerfile
1142
- # dockerContext: .
1143
- numInstances: 1
1144
- healthCheckPath: /health
1145
- ${envVars} # Auto-deploy on push to main branch
1146
- autoDeploy: true
1147
- ${databaseSection}
1148
- # Blueprint metadata
1149
- metadata:
1150
- name: ${projectName}
1151
- description: A Bueno application deployed on Render
1152
- `;
1153
2707
  }
1154
- function getFlyTomlTemplate(projectName) {
1155
- const kebabName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
1156
- return `# ${projectName} - Fly.io Deployment Configuration
1157
- # https://fly.io/docs/reference/configuration/
1158
-
1159
- app = "${kebabName}"
1160
- primary_region = "sea"
1161
-
1162
- [build]
1163
- dockerfile = "Dockerfile"
1164
-
1165
- [env]
1166
- NODE_ENV = "production"
1167
- BUN_ENV = "production"
1168
- PORT = "3000"
1169
-
1170
- [http_service]
1171
- internal_port = 3000
1172
- force_https = true
1173
- auto_stop_machines = "stop"
1174
- auto_start_machines = true
1175
- min_machines_running = 0
1176
- processes = ["app"]
1177
-
1178
- [http_service.concurrency]
1179
- type = "connections"
1180
- hard_limit = 100
1181
- soft_limit = 80
1182
-
1183
- [[http_service.checks]]
1184
- grace_period = "10s"
1185
- interval = "30s"
1186
- method = "GET"
1187
- timeout = "5s"
1188
- path = "/health"
1189
-
1190
- [http_service.checks.headers]
1191
- Content-Type = "application/json"
1192
-
1193
- [[vm]]
1194
- cpu_kind = "shared"
1195
- cpus = 1
1196
- memory_mb = 512
1197
-
1198
- [[mounts]]
1199
- source = "data"
1200
- destination = "/app/data"
1201
- initial_size = "1GB"
1202
-
1203
- # Scale configuration
1204
- # Use: fly scale count 2 # Scale to 2 machines
1205
- # Use: fly scale vm shared-cpu-2x --memory 1024 # Upgrade VM
1206
-
1207
- # Secrets (set via: fly secrets set KEY=VALUE)
1208
- # DATABASE_URL=your-database-url
1209
- # Any other sensitive environment variables
1210
- `;
2708
+ function spinner(text, options) {
2709
+ return new Spinner({ text, ...options }).start();
1211
2710
  }
1212
- function getRailwayTomlTemplate(projectName) {
1213
- const kebabName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
1214
- return `# ${projectName} - Railway Deployment Configuration
1215
- # https://docs.railway.app/reference/config-as-code
1216
-
1217
- [build]
1218
- builder = "DOCKERFILE"
1219
- dockerfilePath = "Dockerfile"
1220
-
1221
- [deploy]
1222
- startCommand = "bun run dist/main.js"
1223
- healthcheckPath = "/health"
1224
- healthcheckTimeout = 300
1225
- restartPolicyType = "ON_FAILURE"
1226
- restartPolicyMaxRetries = 3
1227
-
1228
- # Environment variables
1229
- # Set these in Railway dashboard or via CLI:
1230
- # railway variables set NODE_ENV=production
1231
- # railway variables set DATABASE_URL=your-database-url
1232
-
1233
- [[services]]
1234
- name = "${kebabName}"
1235
-
1236
- [services.variables]
1237
- NODE_ENV = "production"
1238
- BUN_ENV = "production"
1239
- PORT = "3000"
1240
-
1241
- # Health check configuration
1242
- [[services.healthchecks]]
1243
- path = "/health"
1244
- interval = 30
1245
- timeout = 10
1246
- threshold = 3
1247
2711
 
1248
- # Resource configuration
1249
- # Adjust in Railway dashboard or via CLI:
1250
- # 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
+ }
1251
2771
 
1252
- # Scaling configuration
1253
- # Use Railway's autoscaling in dashboard:
1254
- # Min instances: 0 (scale to zero)
1255
- # Max instances: 3
1256
- # Target CPU: 70%
1257
- # Target Memory: 80%
1258
- `;
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
+ }
1259
2781
  }
1260
- function getDeployTemplate(platform, projectName, database) {
1261
- switch (platform) {
1262
- case "render":
1263
- return getRenderYamlTemplate(projectName, database);
1264
- case "fly":
1265
- return getFlyTomlTemplate(projectName);
1266
- case "railway":
1267
- return getRailwayTomlTemplate(projectName);
1268
- default:
1269
- throw new Error(`Unknown deployment platform: ${platform}`);
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
+ }
1270
2810
  }
2811
+ await walk(dirPath);
2812
+ return files;
1271
2813
  }
1272
- function getDeployFilename(platform) {
1273
- switch (platform) {
1274
- case "render":
1275
- return "render.yaml";
1276
- case "fly":
1277
- return "fly.toml";
1278
- case "railway":
1279
- return "railway.toml";
1280
- default:
1281
- throw new Error(`Unknown deployment platform: ${platform}`);
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
+ }
1282
2849
  }
2850
+ return false;
1283
2851
  }
1284
- function getDeployPlatformName(platform) {
1285
- switch (platform) {
1286
- case "render":
1287
- return "Render.com";
1288
- case "fly":
1289
- return "Fly.io";
1290
- case "railway":
1291
- return "Railway";
1292
- default:
1293
- return platform;
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
+ });
1294
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();
1295
2918
  }
1296
2919
 
1297
2920
  // src/cli/commands/new.ts
2921
+ init_version();
2922
+ init_templates();
2923
+ init_templates();
2924
+ init_templates();
1298
2925
  function validateProjectName(name) {
1299
2926
  if (!name || name.length === 0) {
1300
2927
  return "Project name is required";
@@ -1310,27 +2937,26 @@ function validateProjectName(name) {
1310
2937
  }
1311
2938
  return true;
1312
2939
  }
1313
- function getPackageJsonTemplate(config) {
1314
- const dependencies = {};
1315
- if (!config.link) {
1316
- 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"];
1317
2947
  }
1318
2948
  const devDependencies = {
1319
2949
  "@types/bun": "latest",
1320
- typescript: "^5.3.0"
2950
+ typescript: "^5.3.0",
2951
+ ...template.devDependencies || {}
1321
2952
  };
1322
- if (config.template === "fullstack" || config.template === "default") {
1323
- dependencies.zod = "^4.0.0";
1324
- }
1325
2953
  const scripts = {
1326
2954
  dev: "bun run --watch server/main.ts",
1327
2955
  build: "bun build ./server/main.ts --outdir ./dist --target bun",
1328
2956
  start: "bun run dist/main.js",
1329
- test: "bun test"
2957
+ test: "bun test",
2958
+ ...template.scripts || {}
1330
2959
  };
1331
- if (config.template === "fullstack") {
1332
- scripts["dev:frontend"] = "bun run --watch client/index.html";
1333
- }
1334
2960
  return JSON.stringify({
1335
2961
  name: kebabCase(config.name),
1336
2962
  version: "0.1.0",
@@ -1359,121 +2985,8 @@ function getTsConfigTemplate() {
1359
2985
  exclude: ["node_modules", "dist"]
1360
2986
  }, null, 2);
1361
2987
  }
1362
- function getMainTemplate(config) {
1363
- if (config.template === "minimal") {
1364
- return `import { createServer } from '@buenojs/bueno';
1365
-
1366
- const app = createServer();
1367
-
1368
- app.router.get('/', () => {
1369
- return { message: 'Hello, Bueno!' };
1370
- });
1371
-
1372
- await app.listen(3000);
1373
- `;
1374
- }
1375
- return `import { createApp, Module, Controller, Get, Injectable } from '@buenojs/bueno';
1376
- import type { Context } from '@buenojs/bueno';
1377
-
1378
- // Services
1379
- @Injectable()
1380
- export class AppService {
1381
- findAll() {
1382
- return { message: 'Welcome to Bueno!', items: [] };
1383
- }
1384
- }
1385
-
1386
- // Controllers
1387
- @Controller()
1388
- export class AppController {
1389
- constructor(private readonly appService: AppService) {}
1390
-
1391
- @Get()
1392
- hello() {
1393
- return new Response(\`<html>
1394
- <head>
1395
- <title>Welcome to Bueno</title>
1396
- <style>
1397
- body { font-family: system-ui, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
1398
- h1 { color: #2563eb; }
1399
- code { background: #f3f4f6; padding: 2px 6px; border-radius: 4px; }
1400
- pre { background: #f3f4f6; padding: 16px; border-radius: 8px; overflow-x: auto; }
1401
- a { color: #2563eb; }
1402
- </style>
1403
- </head>
1404
- <body>
1405
- <h1>\uD83C\uDF89 Welcome to Bueno Framework!</h1>
1406
- <p>Your Bun-native full-stack framework is running successfully.</p>
1407
-
1408
- <h2>Getting Started</h2>
1409
- <ul>
1410
- <li>Edit <code>server/main.ts</code> to modify this app</li>
1411
- <li>Add routes using the <code>@Get()</code>, <code>@Post()</code> decorators</li>
1412
- <li>Create services with <code>@Injectable()</code> and inject them in controllers</li>
1413
- </ul>
1414
-
1415
- <h2>Documentation</h2>
1416
- <p>Visit <a href="https://buenojs.dev">https://buenojs.dev</a> for full documentation.</p>
1417
-
1418
- <h2>Quick Example</h2>
1419
- <pre><code>@Controller('/api')
1420
- class MyController {
1421
- @Get('/users')
1422
- getUsers() {
1423
- return { users: [] };
1424
- }
1425
- }</code></pre>
1426
- </body>
1427
- </html>\`, {
1428
- headers: { 'Content-Type': 'text/html; charset=utf-8' }
1429
- });
1430
- }
1431
-
1432
- @Get('health')
1433
- health(ctx: Context) {
1434
- return { status: 'ok', timestamp: new Date().toISOString() };
1435
- }
1436
- }
1437
-
1438
- // Module
1439
- @Module({
1440
- controllers: [AppController],
1441
- providers: [AppService],
1442
- })
1443
- export class AppModule {}
1444
-
1445
- // Bootstrap
1446
- const app = createApp(AppModule);
1447
- await app.listen(3000);
1448
- `;
1449
- }
1450
- function getConfigTemplate(config) {
1451
- const dbConfig = config.database === "sqlite" ? `{ url: 'sqlite:./data.db' }` : `{ url: process.env.DATABASE_URL ?? '${config.database}://localhost/${kebabCase(config.name)}' }`;
1452
- return `import { defineConfig } from '@buenojs/bueno';
1453
-
1454
- export default defineConfig({
1455
- server: {
1456
- port: 3000,
1457
- host: 'localhost',
1458
- },
1459
-
1460
- database: ${dbConfig},
1461
-
1462
- logger: {
1463
- level: 'info',
1464
- pretty: true,
1465
- },
1466
-
1467
- health: {
1468
- enabled: true,
1469
- healthPath: '/health',
1470
- readyPath: '/ready',
1471
- },
1472
- });
1473
- `;
1474
- }
1475
2988
  function getEnvExampleTemplate(config) {
1476
- if (config.database === "sqlite") {
2989
+ if (config.database === "none" || config.database === "sqlite") {
1477
2990
  return `# Bueno Environment Variables
1478
2991
  NODE_ENV=development
1479
2992
  `;
@@ -1519,9 +3032,16 @@ coverage/
1519
3032
  `;
1520
3033
  }
1521
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
+ };
1522
3042
  return `# ${config.name}
1523
3043
 
1524
- A Bueno application.
3044
+ A Bueno application - ${templateDescriptions[config.template]}.
1525
3045
 
1526
3046
  ## Getting Started
1527
3047
 
@@ -1553,11 +3073,43 @@ bun run start
1553
3073
 
1554
3074
  ## Learn More
1555
3075
 
1556
- - [Bueno Documentation](https://github.com/sivaraj/bueno#readme)
3076
+ - [Bueno Documentation](https://bueno.github.io)
1557
3077
  - [Bun Documentation](https://bun.sh/docs)
1558
3078
  `;
1559
3079
  }
1560
- 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) {
1561
3113
  const tasks = [];
1562
3114
  tasks.push({
1563
3115
  text: "Creating project structure",
@@ -1572,12 +3124,15 @@ async function createProjectFiles(projectPath, config) {
1572
3124
  await createDirectory(joinPaths(projectPath, "server", "config"));
1573
3125
  await createDirectory(joinPaths(projectPath, "tests", "unit"));
1574
3126
  await createDirectory(joinPaths(projectPath, "tests", "integration"));
3127
+ for (const dir of templateResult.directories) {
3128
+ await createDirectory(joinPaths(projectPath, dir));
3129
+ }
1575
3130
  }
1576
3131
  });
1577
3132
  tasks.push({
1578
3133
  text: "Creating package.json",
1579
3134
  task: async () => {
1580
- await writeFile(joinPaths(projectPath, "package.json"), getPackageJsonTemplate(config));
3135
+ await writeFile(joinPaths(projectPath, "package.json"), getPackageJsonTemplate(config, templateResult));
1581
3136
  }
1582
3137
  });
1583
3138
  tasks.push({
@@ -1586,18 +3141,22 @@ async function createProjectFiles(projectPath, config) {
1586
3141
  await writeFile(joinPaths(projectPath, "tsconfig.json"), getTsConfigTemplate());
1587
3142
  }
1588
3143
  });
1589
- tasks.push({
1590
- text: "Creating server/main.ts",
1591
- task: async () => {
1592
- await writeFile(joinPaths(projectPath, "server", "main.ts"), getMainTemplate(config));
1593
- }
1594
- });
1595
- tasks.push({
1596
- text: "Creating bueno.config.ts",
1597
- task: async () => {
1598
- await writeFile(joinPaths(projectPath, "bueno.config.ts"), getConfigTemplate(config));
1599
- }
1600
- });
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
+ }
1601
3160
  tasks.push({
1602
3161
  text: "Creating .env.example",
1603
3162
  task: async () => {
@@ -1616,11 +3175,11 @@ async function createProjectFiles(projectPath, config) {
1616
3175
  await writeFile(joinPaths(projectPath, "README.md"), getReadmeTemplate(config));
1617
3176
  }
1618
3177
  });
1619
- if (config.docker) {
3178
+ if (config.docker && config.template !== "website") {
1620
3179
  tasks.push({
1621
3180
  text: "Creating Dockerfile",
1622
3181
  task: async () => {
1623
- 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));
1624
3183
  }
1625
3184
  });
1626
3185
  tasks.push({
@@ -1632,13 +3191,13 @@ async function createProjectFiles(projectPath, config) {
1632
3191
  tasks.push({
1633
3192
  text: "Creating docker-compose.yml",
1634
3193
  task: async () => {
1635
- 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));
1636
3195
  }
1637
3196
  });
1638
3197
  tasks.push({
1639
3198
  text: "Creating .env.docker",
1640
3199
  task: async () => {
1641
- 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));
1642
3201
  }
1643
3202
  });
1644
3203
  }
@@ -1647,7 +3206,7 @@ async function createProjectFiles(projectPath, config) {
1647
3206
  tasks.push({
1648
3207
  text: `Creating ${filename} for ${getDeployPlatformName(platform)}`,
1649
3208
  task: async () => {
1650
- 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));
1651
3210
  }
1652
3211
  });
1653
3212
  }
@@ -1704,37 +3263,24 @@ async function handleNew(args) {
1704
3263
  }
1705
3264
  if (!useDefaults && isInteractive()) {
1706
3265
  if (!template) {
1707
- template = await select("Select a template:", [
1708
- { value: "default", name: "Default - Standard project with modules and database" },
1709
- { value: "minimal", name: "Minimal - Bare minimum project structure" },
1710
- { value: "fullstack", name: "Fullstack - Full-stack project with SSR and auth" },
1711
- { value: "api", name: "API - API-only project without frontend" }
1712
- ], { default: "default" });
3266
+ template = await select("Select a template:", getTemplateOptions(), { default: "default" });
1713
3267
  }
1714
3268
  if ((template === "fullstack" || template === "default") && !framework) {
1715
- framework = await select("Select a frontend framework:", [
1716
- { value: "react", name: "React" },
1717
- { value: "vue", name: "Vue" },
1718
- { value: "svelte", name: "Svelte" },
1719
- { value: "solid", name: "Solid" }
1720
- ], { default: "react" });
3269
+ framework = await select("Select a frontend framework:", getFrontendOptions(), { default: "react" });
1721
3270
  }
1722
- if (!database) {
1723
- database = await select("Select a database:", [
1724
- { value: "sqlite", name: "SQLite - Local file-based database" },
1725
- { value: "postgresql", name: "PostgreSQL - Production-ready relational database" },
1726
- { value: "mysql", name: "MySQL - Popular relational database" }
1727
- ], { default: "sqlite" });
3271
+ if (template !== "website" && !database) {
3272
+ database = await select("Select a database:", getDatabaseOptions(), { default: "sqlite" });
1728
3273
  }
1729
3274
  }
1730
3275
  template = template || "default";
1731
3276
  framework = framework || "react";
1732
3277
  database = database || "sqlite";
3278
+ const isWebsite = template === "website";
1733
3279
  const config = {
1734
3280
  name,
1735
3281
  template,
1736
- framework,
1737
- database,
3282
+ framework: isWebsite ? "none" : framework,
3283
+ database: isWebsite ? "none" : database,
1738
3284
  skipInstall,
1739
3285
  skipGit,
1740
3286
  docker,
@@ -1748,18 +3294,20 @@ async function handleNew(args) {
1748
3294
  cliConsole.header(`Creating a new Bueno project: ${colors.cyan(name)}`);
1749
3295
  const rows = [
1750
3296
  ["Template", template],
1751
- ["Framework", framework],
1752
- ["Database", database],
3297
+ ["Framework", isWebsite ? "N/A (Static Site)" : framework],
3298
+ ["Database", isWebsite ? "N/A" : database],
1753
3299
  ["Docker", docker ? colors.green("Yes") : colors.red("No")],
1754
3300
  ["Deploy", deploy.length > 0 ? colors.green(deploy.map(getDeployPlatformName).join(", ")) : colors.red("None")],
1755
3301
  ["Install dependencies", skipInstall ? colors.red("No") : colors.green("Yes")],
1756
- ["Use local package", link ? colors.green("Yes (bun link)") : colors.red("No")],
1757
- ["Initialize git", skipGit ? colors.red("No") : colors.green("Yes")]
3302
+ ["Use local package", link ? colors.green("Yes (bun link)") : colors.red("No")]
1758
3303
  ];
1759
3304
  printTable(["Setting", "Value"], rows);
1760
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);
1761
3309
  cliConsole.subheader("Creating project files...");
1762
- await createProjectFiles(projectPath, config);
3310
+ await createProjectFiles(projectPath, config, templateResult);
1763
3311
  if (!skipInstall) {
1764
3312
  cliConsole.subheader("Installing dependencies...");
1765
3313
  const installSpinner = spinner("Running bun install...");
@@ -1798,35 +3346,17 @@ async function handleNew(args) {
1798
3346
  }
1799
3347
  }
1800
3348
  }
1801
- if (!skipGit) {
1802
- cliConsole.subheader("Initializing git repository...");
1803
- const gitSpinner = spinner("Running git init...");
1804
- try {
1805
- const proc = Bun.spawn(["git", "init"], {
1806
- cwd: projectPath,
1807
- stdout: "pipe",
1808
- stderr: "pipe"
1809
- });
1810
- const exitCode = await proc.exited;
1811
- if (exitCode === 0) {
1812
- Bun.spawn(["git", "add", "."], { cwd: projectPath });
1813
- Bun.spawn(["git", "commit", "-m", "Initial commit from Bueno CLI"], {
1814
- cwd: projectPath
1815
- });
1816
- gitSpinner.success("Git repository initialized");
1817
- } else {
1818
- gitSpinner.warn("Failed to initialize git. Run `git init` manually.");
1819
- }
1820
- } catch {
1821
- gitSpinner.warn("Failed to initialize git. Run `git init` manually.");
1822
- }
1823
- }
1824
3349
  cliConsole.log("");
1825
3350
  cliConsole.success(`Project created successfully!`);
1826
3351
  cliConsole.log("");
1827
3352
  cliConsole.log("Next steps:");
1828
3353
  cliConsole.log(` ${colors.cyan(`cd ${kebabCase(name)}`)}`);
1829
- 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
+ }
1830
3360
  cliConsole.log("");
1831
3361
  cliConsole.log(`Documentation: ${colors.dim("https://github.com/sivaraj/bueno")}`);
1832
3362
  }
@@ -1845,19 +3375,19 @@ defineCommand({
1845
3375
  name: "template",
1846
3376
  alias: "t",
1847
3377
  type: "string",
1848
- description: "Project template (default, minimal, fullstack, api)"
3378
+ description: "Project template (default, minimal, fullstack, api, website)"
1849
3379
  },
1850
3380
  {
1851
3381
  name: "framework",
1852
3382
  alias: "f",
1853
3383
  type: "string",
1854
- description: "Frontend framework (react, vue, svelte, solid)"
3384
+ description: "Frontend framework (react, vue, svelte, solid, none)"
1855
3385
  },
1856
3386
  {
1857
3387
  name: "database",
1858
3388
  alias: "d",
1859
3389
  type: "string",
1860
- description: "Database driver (sqlite, postgresql, mysql)"
3390
+ description: "Database driver (sqlite, postgresql, mysql, none)"
1861
3391
  },
1862
3392
  {
1863
3393
  name: "skip-install",
@@ -1869,7 +3399,7 @@ defineCommand({
1869
3399
  name: "skip-git",
1870
3400
  type: "boolean",
1871
3401
  default: false,
1872
- description: "Skip git initialization"
3402
+ description: "Skip git initialization (deprecated - git init is no longer automatic)"
1873
3403
  },
1874
3404
  {
1875
3405
  name: "docker",
@@ -1901,6 +3431,7 @@ defineCommand({
1901
3431
  "bueno new my-api --template api",
1902
3432
  "bueno new my-fullstack --template fullstack --framework react",
1903
3433
  "bueno new my-project --database postgresql",
3434
+ "bueno new my-website --template website",
1904
3435
  "bueno new my-app --docker",
1905
3436
  "bueno new my-app --docker --database postgresql",
1906
3437
  "bueno new my-app --deploy render",
@@ -1914,7 +3445,7 @@ defineCommand({
1914
3445
  }, handleNew);
1915
3446
 
1916
3447
  // src/cli/commands/generate.ts
1917
- var GENERATOR_ALIASES = {
3448
+ var GENERATOR_ALIASES2 = {
1918
3449
  c: "controller",
1919
3450
  s: "service",
1920
3451
  m: "module",
@@ -2141,10 +3672,10 @@ export default createMigration('{{migrationId}}', '{{migrationName}}')
2141
3672
  };
2142
3673
  return templates[type];
2143
3674
  }
2144
- function getFileExtension(type) {
3675
+ function getFileExtension2(type) {
2145
3676
  return type === "dto" ? ".dto.ts" : ".ts";
2146
3677
  }
2147
- function getDefaultDirectory(type) {
3678
+ function getDefaultDirectory2(type) {
2148
3679
  switch (type) {
2149
3680
  case "controller":
2150
3681
  case "service":
@@ -2174,7 +3705,7 @@ async function generateFile(config) {
2174
3705
  throw new CLIError("Not in a Bueno project directory", "NOT_FOUND" /* NOT_FOUND */);
2175
3706
  }
2176
3707
  const kebabName = kebabCase(name);
2177
- const defaultDir = getDefaultDirectory(type);
3708
+ const defaultDir = getDefaultDirectory2(type);
2178
3709
  let targetDir;
2179
3710
  if (customPath) {
2180
3711
  targetDir = joinPaths(projectRoot, customPath);
@@ -2185,7 +3716,7 @@ async function generateFile(config) {
2185
3716
  } else {
2186
3717
  targetDir = joinPaths(projectRoot, "server", defaultDir, kebabName);
2187
3718
  }
2188
- const fileName = type === "migration" ? `${generateMigrationId()}_${kebabName}${getFileExtension(type)}` : `${kebabName}${getFileExtension(type)}`;
3719
+ const fileName = type === "migration" ? `${generateMigrationId2()}_${kebabName}${getFileExtension2(type)}` : `${kebabName}${getFileExtension2(type)}`;
2189
3720
  const filePath = joinPaths(targetDir, fileName);
2190
3721
  if (!force && await fileExists(filePath)) {
2191
3722
  if (isInteractive()) {
@@ -2203,7 +3734,7 @@ async function generateFile(config) {
2203
3734
  module: module ?? "",
2204
3735
  path: customPath ?? kebabName,
2205
3736
  service: type === "controller" ? name : "",
2206
- migrationId: generateMigrationId(),
3737
+ migrationId: generateMigrationId2(),
2207
3738
  migrationName: name,
2208
3739
  tableName: kebabName
2209
3740
  });
@@ -2218,7 +3749,7 @@ ${colors.bold("File:")} ${filePath}`);
2218
3749
  }
2219
3750
  return filePath;
2220
3751
  }
2221
- function generateMigrationId() {
3752
+ function generateMigrationId2() {
2222
3753
  const now = new Date;
2223
3754
  const year = now.getFullYear();
2224
3755
  const month = String(now.getMonth() + 1).padStart(2, "0");
@@ -2233,7 +3764,7 @@ async function handleGenerate(args) {
2233
3764
  if (!typeArg) {
2234
3765
  throw new CLIError("Generator type is required. Usage: bueno generate <type> <name>", "INVALID_ARGS" /* INVALID_ARGS */);
2235
3766
  }
2236
- const type = GENERATOR_ALIASES[typeArg] ?? typeArg;
3767
+ const type = GENERATOR_ALIASES2[typeArg] ?? typeArg;
2237
3768
  if (!getTemplate(type)) {
2238
3769
  throw new CLIError(`Unknown generator type: ${typeArg}. Available types: controller, service, module, guard, interceptor, pipe, filter, dto, middleware, migration`, "INVALID_ARGS" /* INVALID_ARGS */);
2239
3770
  }
@@ -2324,7 +3855,7 @@ defineCommand({
2324
3855
  }, handleGenerate);
2325
3856
 
2326
3857
  // src/cli/commands/migration.ts
2327
- function generateMigrationId2() {
3858
+ function generateMigrationId3() {
2328
3859
  const now = new Date;
2329
3860
  const year = now.getFullYear();
2330
3861
  const month = String(now.getMonth() + 1).padStart(2, "0");
@@ -2370,7 +3901,7 @@ function parseMigrationFile(filename) {
2370
3901
  }
2371
3902
  async function createMigration(name, dryRun) {
2372
3903
  const migrationsDir = await getMigrationsDir();
2373
- const id = generateMigrationId2();
3904
+ const id = generateMigrationId3();
2374
3905
  const kebabName = name.toLowerCase().replace(/\s+/g, "-");
2375
3906
  const fileName = `${id}_${kebabName}.ts`;
2376
3907
  const filePath = joinPaths(migrationsDir, fileName);
@@ -3211,8 +4742,11 @@ Bueno CLI - Available Commands
3211
4742
  cliConsole.log(generateGlobalHelpText(registry.getAll()));
3212
4743
  }
3213
4744
  });
4745
+ // src/cli/utils/index.ts
4746
+ init_version();
4747
+
3214
4748
  // src/cli/index.ts
3215
- var packageJson = JSON.parse(readFileSync2(join2(import.meta.dir, "../../package.json"), "utf-8"));
4749
+ var packageJson = JSON.parse(readFileSync3(join3(import.meta.dir, "../../package.json"), "utf-8"));
3216
4750
  var VERSION = packageJson.version;
3217
4751
  class CLIError extends Error {
3218
4752
  type;