@buenojs/bueno 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/.env.example +109 -0
  2. package/.github/workflows/ci.yml +31 -0
  3. package/LICENSE +21 -0
  4. package/README.md +892 -0
  5. package/architecture.md +652 -0
  6. package/bun.lock +70 -0
  7. package/dist/cli/index.js +3233 -0
  8. package/dist/index.js +9014 -0
  9. package/package.json +77 -0
  10. package/src/cache/index.ts +795 -0
  11. package/src/cli/ARCHITECTURE.md +837 -0
  12. package/src/cli/bin.ts +10 -0
  13. package/src/cli/commands/build.ts +425 -0
  14. package/src/cli/commands/dev.ts +248 -0
  15. package/src/cli/commands/generate.ts +541 -0
  16. package/src/cli/commands/help.ts +55 -0
  17. package/src/cli/commands/index.ts +112 -0
  18. package/src/cli/commands/migration.ts +355 -0
  19. package/src/cli/commands/new.ts +804 -0
  20. package/src/cli/commands/start.ts +208 -0
  21. package/src/cli/core/args.ts +283 -0
  22. package/src/cli/core/console.ts +349 -0
  23. package/src/cli/core/index.ts +60 -0
  24. package/src/cli/core/prompt.ts +424 -0
  25. package/src/cli/core/spinner.ts +265 -0
  26. package/src/cli/index.ts +135 -0
  27. package/src/cli/templates/deploy.ts +295 -0
  28. package/src/cli/templates/docker.ts +307 -0
  29. package/src/cli/templates/index.ts +24 -0
  30. package/src/cli/utils/fs.ts +428 -0
  31. package/src/cli/utils/index.ts +8 -0
  32. package/src/cli/utils/strings.ts +197 -0
  33. package/src/config/env.ts +408 -0
  34. package/src/config/index.ts +506 -0
  35. package/src/config/loader.ts +329 -0
  36. package/src/config/merge.ts +285 -0
  37. package/src/config/types.ts +320 -0
  38. package/src/config/validation.ts +441 -0
  39. package/src/container/forward-ref.ts +143 -0
  40. package/src/container/index.ts +386 -0
  41. package/src/context/index.ts +360 -0
  42. package/src/database/index.ts +1142 -0
  43. package/src/database/migrations/index.ts +371 -0
  44. package/src/database/schema/index.ts +619 -0
  45. package/src/frontend/api-routes.ts +640 -0
  46. package/src/frontend/bundler.ts +643 -0
  47. package/src/frontend/console-client.ts +419 -0
  48. package/src/frontend/console-stream.ts +587 -0
  49. package/src/frontend/dev-server.ts +846 -0
  50. package/src/frontend/file-router.ts +611 -0
  51. package/src/frontend/frameworks/index.ts +106 -0
  52. package/src/frontend/frameworks/react.ts +85 -0
  53. package/src/frontend/frameworks/solid.ts +104 -0
  54. package/src/frontend/frameworks/svelte.ts +110 -0
  55. package/src/frontend/frameworks/vue.ts +92 -0
  56. package/src/frontend/hmr-client.ts +663 -0
  57. package/src/frontend/hmr.ts +728 -0
  58. package/src/frontend/index.ts +342 -0
  59. package/src/frontend/islands.ts +552 -0
  60. package/src/frontend/isr.ts +555 -0
  61. package/src/frontend/layout.ts +475 -0
  62. package/src/frontend/ssr/react.ts +446 -0
  63. package/src/frontend/ssr/solid.ts +523 -0
  64. package/src/frontend/ssr/svelte.ts +546 -0
  65. package/src/frontend/ssr/vue.ts +504 -0
  66. package/src/frontend/ssr.ts +699 -0
  67. package/src/frontend/types.ts +2274 -0
  68. package/src/health/index.ts +604 -0
  69. package/src/index.ts +410 -0
  70. package/src/lock/index.ts +587 -0
  71. package/src/logger/index.ts +444 -0
  72. package/src/logger/transports/index.ts +969 -0
  73. package/src/metrics/index.ts +494 -0
  74. package/src/middleware/built-in.ts +360 -0
  75. package/src/middleware/index.ts +94 -0
  76. package/src/modules/filters.ts +458 -0
  77. package/src/modules/guards.ts +405 -0
  78. package/src/modules/index.ts +1256 -0
  79. package/src/modules/interceptors.ts +574 -0
  80. package/src/modules/lazy.ts +418 -0
  81. package/src/modules/lifecycle.ts +478 -0
  82. package/src/modules/metadata.ts +90 -0
  83. package/src/modules/pipes.ts +626 -0
  84. package/src/router/index.ts +339 -0
  85. package/src/router/linear.ts +371 -0
  86. package/src/router/regex.ts +292 -0
  87. package/src/router/tree.ts +562 -0
  88. package/src/rpc/index.ts +1263 -0
  89. package/src/security/index.ts +436 -0
  90. package/src/ssg/index.ts +631 -0
  91. package/src/storage/index.ts +456 -0
  92. package/src/telemetry/index.ts +1097 -0
  93. package/src/testing/index.ts +1586 -0
  94. package/src/types/index.ts +236 -0
  95. package/src/types/optional-deps.d.ts +219 -0
  96. package/src/validation/index.ts +276 -0
  97. package/src/websocket/index.ts +1004 -0
  98. package/tests/integration/cli.test.ts +1016 -0
  99. package/tests/integration/fullstack.test.ts +234 -0
  100. package/tests/unit/cache.test.ts +174 -0
  101. package/tests/unit/cli-commands.test.ts +892 -0
  102. package/tests/unit/cli.test.ts +1258 -0
  103. package/tests/unit/container.test.ts +279 -0
  104. package/tests/unit/context.test.ts +221 -0
  105. package/tests/unit/database.test.ts +183 -0
  106. package/tests/unit/linear-router.test.ts +280 -0
  107. package/tests/unit/lock.test.ts +336 -0
  108. package/tests/unit/middleware.test.ts +184 -0
  109. package/tests/unit/modules.test.ts +142 -0
  110. package/tests/unit/pubsub.test.ts +257 -0
  111. package/tests/unit/regex-router.test.ts +265 -0
  112. package/tests/unit/router.test.ts +373 -0
  113. package/tests/unit/rpc.test.ts +1248 -0
  114. package/tests/unit/security.test.ts +174 -0
  115. package/tests/unit/telemetry.test.ts +371 -0
  116. package/tests/unit/test-cache.test.ts +110 -0
  117. package/tests/unit/test-database.test.ts +282 -0
  118. package/tests/unit/tree-router.test.ts +325 -0
  119. package/tests/unit/validation.test.ts +794 -0
  120. package/tsconfig.json +27 -0
package/src/cli/bin.ts ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Bueno CLI Executable
4
+ *
5
+ * This is the entry point for the CLI when installed globally or run via npx
6
+ */
7
+
8
+ import { main } from './index';
9
+
10
+ main();
@@ -0,0 +1,425 @@
1
+ /**
2
+ * Build Command
3
+ *
4
+ * Build the application for production
5
+ */
6
+
7
+ import { defineCommand } from './index';
8
+ import { getOption, hasFlag, type ParsedArgs } from '../core/args';
9
+ import { cliConsole, colors, formatSize, formatDuration } from '../core/console';
10
+ import { spinner } from '../core/spinner';
11
+ import {
12
+ fileExists,
13
+ getProjectRoot,
14
+ isBuenoProject,
15
+ joinPaths,
16
+ readFile,
17
+ deleteDirectory,
18
+ listFiles,
19
+ } from '../utils/fs';
20
+ import { CLIError, CLIErrorType } from '../index';
21
+
22
+ /**
23
+ * Build targets
24
+ */
25
+ type BuildTarget = 'bun' | 'node' | 'standalone';
26
+
27
+ /**
28
+ * Cross-compile targets
29
+ */
30
+ type CrossCompileTarget = 'linux-x64' | 'linux-arm64' | 'windows-x64' | 'darwin-x64' | 'darwin-arm64';
31
+
32
+ /**
33
+ * Find the entry point for the application
34
+ */
35
+ async function findEntryPoint(projectRoot: string): Promise<string | null> {
36
+ const possibleEntries = [
37
+ 'server/main.ts',
38
+ 'src/main.ts',
39
+ 'src/index.ts',
40
+ 'main.ts',
41
+ 'index.ts',
42
+ 'server.ts',
43
+ 'app.ts',
44
+ ];
45
+
46
+ for (const entry of possibleEntries) {
47
+ const entryPath = joinPaths(projectRoot, entry);
48
+ if (await fileExists(entryPath)) {
49
+ return entry;
50
+ }
51
+ }
52
+
53
+ return null;
54
+ }
55
+
56
+ /**
57
+ * Handle build command
58
+ */
59
+ async function handleBuild(args: ParsedArgs): Promise<void> {
60
+ // Get options
61
+ const target = getOption(args, 'target', {
62
+ name: 'target',
63
+ alias: 't',
64
+ type: 'string',
65
+ default: 'bun',
66
+ description: '',
67
+ }) as BuildTarget;
68
+
69
+ const outDir = getOption<string>(args, 'outdir', {
70
+ name: 'outdir',
71
+ alias: 'o',
72
+ type: 'string',
73
+ default: './dist',
74
+ description: '',
75
+ });
76
+
77
+ const minify = !hasFlag(args, 'no-minify');
78
+ const sourcemap = hasFlag(args, 'sourcemap');
79
+ const analyze = hasFlag(args, 'analyze');
80
+ const configPath = getOption(args, 'config', {
81
+ name: 'config',
82
+ alias: 'c',
83
+ type: 'string',
84
+ description: '',
85
+ });
86
+
87
+ // Compile options
88
+ const compile = hasFlag(args, 'compile');
89
+ const crossCompile = getOption(args, 'cross-compile', {
90
+ name: 'cross-compile',
91
+ type: 'string',
92
+ description: '',
93
+ }) as CrossCompileTarget | undefined;
94
+ const executableName = getOption<string>(args, 'executable-name', {
95
+ name: 'executable-name',
96
+ type: 'string',
97
+ default: 'main',
98
+ description: '',
99
+ });
100
+
101
+ // Validate cross-compile target
102
+ if (crossCompile) {
103
+ const validCrossCompileTargets: CrossCompileTarget[] = [
104
+ 'linux-x64',
105
+ 'linux-arm64',
106
+ 'windows-x64',
107
+ 'darwin-x64',
108
+ 'darwin-arm64',
109
+ ];
110
+ if (!validCrossCompileTargets.includes(crossCompile)) {
111
+ throw new CLIError(
112
+ `Invalid cross-compile target: ${crossCompile}. Valid targets: ${validCrossCompileTargets.join(', ')}`,
113
+ CLIErrorType.INVALID_ARGS,
114
+ );
115
+ }
116
+ }
117
+
118
+ // Validate that cross-compile requires compile
119
+ if (crossCompile && !compile) {
120
+ throw new CLIError(
121
+ '--cross-compile requires --compile flag',
122
+ CLIErrorType.INVALID_ARGS,
123
+ );
124
+ }
125
+
126
+ // Validate target
127
+ const validTargets: BuildTarget[] = ['bun', 'node', 'standalone'];
128
+ if (!validTargets.includes(target)) {
129
+ throw new CLIError(
130
+ `Invalid target: ${target}. Valid targets: ${validTargets.join(', ')}`,
131
+ CLIErrorType.INVALID_ARGS,
132
+ );
133
+ }
134
+
135
+ // Check if in a Bueno project
136
+ const projectRoot = await getProjectRoot();
137
+ if (!projectRoot) {
138
+ throw new CLIError(
139
+ 'Not in a project directory. Run this command from a Bueno project.',
140
+ CLIErrorType.NOT_FOUND,
141
+ );
142
+ }
143
+
144
+ if (!(await isBuenoProject())) {
145
+ throw new CLIError(
146
+ 'Not a Bueno project. Make sure you have a bueno.config.ts or bueno in your dependencies.',
147
+ CLIErrorType.NOT_FOUND,
148
+ );
149
+ }
150
+
151
+ // Find entry point
152
+ const entryPoint = await findEntryPoint(projectRoot);
153
+ if (!entryPoint) {
154
+ throw new CLIError(
155
+ 'Could not find entry point. Make sure you have a main.ts or index.ts file.',
156
+ CLIErrorType.FILE_NOT_FOUND,
157
+ );
158
+ }
159
+
160
+ // Display build info
161
+ if (compile) {
162
+ cliConsole.header('Compiling Single-File Executable');
163
+ } else {
164
+ cliConsole.header('Building for Production');
165
+ }
166
+ cliConsole.log(`${colors.bold('Entry:')} ${entryPoint}`);
167
+ cliConsole.log(`${colors.bold('Target:')} ${target}`);
168
+ if (compile) {
169
+ cliConsole.log(`${colors.bold('Compile:')} ${colors.green('enabled')}`);
170
+ if (crossCompile) {
171
+ cliConsole.log(`${colors.bold('Cross-compile:')} ${colors.cyan(crossCompile)}`);
172
+ }
173
+ cliConsole.log(`${colors.bold('Executable:')} ${executableName}`);
174
+ }
175
+ cliConsole.log(`${colors.bold('Output:')} ${outDir}`);
176
+ cliConsole.log(`${colors.bold('Minify:')} ${minify ? colors.green('enabled') : colors.red('disabled')}`);
177
+ cliConsole.log(`${colors.bold('Sourcemap:')} ${sourcemap ? colors.green('enabled') : colors.red('disabled')}`);
178
+ cliConsole.log('');
179
+
180
+ const startTime = Date.now();
181
+ const s = spinner('Building...');
182
+
183
+ try {
184
+ // Clean output directory
185
+ const fullOutDir = joinPaths(projectRoot, outDir);
186
+ if (await fileExists(fullOutDir)) {
187
+ await deleteDirectory(fullOutDir);
188
+ }
189
+
190
+ // Handle compile build
191
+ if (compile) {
192
+ // Determine the executable filename
193
+ const isWindows = crossCompile === 'windows-x64';
194
+ const executableFileName = isWindows
195
+ ? `${executableName}.exe`
196
+ : executableName;
197
+ const executablePath = joinPaths(fullOutDir, executableFileName);
198
+
199
+ // Build compile options
200
+ const buildOptions: any = {
201
+ entrypoints: [joinPaths(projectRoot, entryPoint)],
202
+ outdir: fullOutDir,
203
+ target: crossCompile || 'bun',
204
+ minify,
205
+ sourcemap: sourcemap ? 'external' : undefined,
206
+ naming: executableFileName,
207
+ compile: true,
208
+ define: {
209
+ 'process.env.NODE_ENV': '"production"',
210
+ },
211
+ };
212
+
213
+ // Build using Bun with compile
214
+ const buildResult = await Bun.build(buildOptions);
215
+
216
+ if (!buildResult.success) {
217
+ s.error();
218
+ for (const error of buildResult.logs) {
219
+ cliConsole.error(error.message);
220
+ }
221
+ throw new CLIError(
222
+ 'Compile failed',
223
+ CLIErrorType.TEMPLATE_ERROR,
224
+ );
225
+ }
226
+
227
+ const elapsed = Date.now() - startTime;
228
+ s.success(`Compile completed in ${formatDuration(elapsed)}`);
229
+
230
+ // Show output info
231
+ cliConsole.log('');
232
+ cliConsole.log(`${colors.bold('Output Executable:')}`);
233
+
234
+ // Get executable file size
235
+ const fs = require('fs');
236
+ let executableSize = 0;
237
+ try {
238
+ const stat = fs.statSync(executablePath);
239
+ executableSize = stat.size;
240
+ } catch (e) {
241
+ // If we can't stat the file, try to get size from build result
242
+ if (buildResult.outputs.length > 0) {
243
+ executableSize = buildResult.outputs[0].size;
244
+ }
245
+ }
246
+
247
+ cliConsole.log(` ${colors.cyan(executablePath.replace(projectRoot, '.'))} ${colors.dim(`(${formatSize(executableSize)})`)}`);
248
+ cliConsole.log('');
249
+
250
+ // Show success message with run instructions
251
+ cliConsole.success('Single-file executable created successfully!');
252
+ if (crossCompile) {
253
+ cliConsole.log(`${colors.bold('Target Platform:')} ${crossCompile}`);
254
+ }
255
+ cliConsole.log('');
256
+ cliConsole.info('You can run the executable directly:');
257
+ if (isWindows) {
258
+ cliConsole.log(colors.cyan(` .${outDir}/${executableFileName}`));
259
+ } else {
260
+ cliConsole.log(colors.cyan(` .${outDir}/${executableFileName}`));
261
+ }
262
+
263
+ // Analyze if requested
264
+ if (analyze) {
265
+ cliConsole.log('');
266
+ cliConsole.header('Bundle Analysis');
267
+ cliConsole.log('Output:');
268
+ for (const entry of buildResult.outputs) {
269
+ cliConsole.log(` ${entry.path} (${formatSize(entry.size)})`);
270
+ }
271
+ }
272
+
273
+ return;
274
+ }
275
+
276
+ // Build using Bun (non-compile)
277
+ const buildResult = await Bun.build({
278
+ entrypoints: [joinPaths(projectRoot, entryPoint)],
279
+ outdir: fullOutDir,
280
+ target: target === 'node' ? 'node' : 'bun',
281
+ minify,
282
+ sourcemap: sourcemap ? 'external' : undefined,
283
+ splitting: true,
284
+ format: 'esm',
285
+ external: target === 'standalone' ? [] : ['bun:*'],
286
+ define: {
287
+ 'process.env.NODE_ENV': '"production"',
288
+ },
289
+ });
290
+
291
+ if (!buildResult.success) {
292
+ s.error();
293
+ for (const error of buildResult.logs) {
294
+ cliConsole.error(error.message);
295
+ }
296
+ throw new CLIError(
297
+ 'Build failed',
298
+ CLIErrorType.TEMPLATE_ERROR,
299
+ );
300
+ }
301
+
302
+ const elapsed = Date.now() - startTime;
303
+
304
+ // Get output files info
305
+ const outputFiles = await listFiles(fullOutDir, { recursive: true });
306
+ const totalSize = outputFiles.reduce((acc, file) => {
307
+ const stat = require('fs').statSync(file);
308
+ return acc + stat.size;
309
+ }, 0);
310
+
311
+ s.success(`Build completed in ${formatDuration(elapsed)}`);
312
+
313
+ // Show output info
314
+ cliConsole.log('');
315
+ cliConsole.log(`${colors.bold('Output Files:')}`);
316
+ for (const file of outputFiles.slice(0, 10)) {
317
+ const stat = require('fs').statSync(file);
318
+ const relativePath = file.replace(projectRoot, '.');
319
+ cliConsole.log(` ${colors.dim(relativePath)} ${colors.dim(`(${formatSize(stat.size)})`)}`);
320
+ }
321
+ if (outputFiles.length > 10) {
322
+ cliConsole.log(` ${colors.dim(`... and ${outputFiles.length - 10} more files`)}`);
323
+ }
324
+ cliConsole.log('');
325
+ cliConsole.log(`${colors.bold('Total Size:')} ${formatSize(totalSize)}`);
326
+
327
+ // Analyze if requested
328
+ if (analyze) {
329
+ cliConsole.log('');
330
+ cliConsole.header('Bundle Analysis');
331
+ cliConsole.log('Entry points:');
332
+ for (const entry of buildResult.outputs) {
333
+ cliConsole.log(` ${entry.path} (${formatSize(entry.size)})`);
334
+ }
335
+ }
336
+
337
+ // Show standalone build info
338
+ if (target === 'standalone') {
339
+ cliConsole.log('');
340
+ cliConsole.info('Standalone bundle created. You can run it with:');
341
+ cliConsole.log(colors.cyan(` bun .${outDir}/${entryPoint.replace('.ts', '.js')}`));
342
+ }
343
+
344
+ } catch (error) {
345
+ s.error();
346
+ throw error;
347
+ }
348
+ }
349
+
350
+ // Register the command
351
+ defineCommand(
352
+ {
353
+ name: 'build',
354
+ description: 'Build the application for production',
355
+ options: [
356
+ {
357
+ name: 'target',
358
+ alias: 't',
359
+ type: 'string',
360
+ default: 'bun',
361
+ description: 'Build target (bun, node, standalone)',
362
+ },
363
+ {
364
+ name: 'outdir',
365
+ alias: 'o',
366
+ type: 'string',
367
+ default: './dist',
368
+ description: 'Output directory',
369
+ },
370
+ {
371
+ name: 'no-minify',
372
+ type: 'boolean',
373
+ default: false,
374
+ description: 'Disable minification',
375
+ },
376
+ {
377
+ name: 'sourcemap',
378
+ type: 'boolean',
379
+ default: false,
380
+ description: 'Generate source maps',
381
+ },
382
+ {
383
+ name: 'analyze',
384
+ type: 'boolean',
385
+ default: false,
386
+ description: 'Analyze bundle size',
387
+ },
388
+ {
389
+ name: 'config',
390
+ alias: 'c',
391
+ type: 'string',
392
+ description: 'Path to config file',
393
+ },
394
+ {
395
+ name: 'compile',
396
+ type: 'boolean',
397
+ default: false,
398
+ description: 'Create a single-file executable using Bun compile',
399
+ },
400
+ {
401
+ name: 'cross-compile',
402
+ type: 'string',
403
+ description: 'Cross-compile for different platforms (linux-x64, linux-arm64, windows-x64, darwin-x64, darwin-arm64)',
404
+ },
405
+ {
406
+ name: 'executable-name',
407
+ type: 'string',
408
+ default: 'main',
409
+ description: 'Custom name for the output executable (default: main)',
410
+ },
411
+ ],
412
+ examples: [
413
+ 'bueno build',
414
+ 'bueno build --target node',
415
+ 'bueno build --target standalone',
416
+ 'bueno build --sourcemap',
417
+ 'bueno build --analyze',
418
+ 'bueno build --compile',
419
+ 'bueno build --compile --outdir ./bin',
420
+ 'bueno build --compile --cross-compile linux-x64',
421
+ 'bueno build --compile --executable-name myapp',
422
+ ],
423
+ },
424
+ handleBuild,
425
+ );
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Dev Command
3
+ *
4
+ * Start the development server with hot reload
5
+ */
6
+
7
+ import { defineCommand } from './index';
8
+ import { getOption, hasFlag, type ParsedArgs } from '../core/args';
9
+ import { cliConsole, colors } from '../core/console';
10
+ import { spinner } from '../core/spinner';
11
+ import {
12
+ fileExists,
13
+ getProjectRoot,
14
+ isBuenoProject,
15
+ joinPaths,
16
+ findFileUp,
17
+ readFile,
18
+ } from '../utils/fs';
19
+ import { CLIError, CLIErrorType } from '../index';
20
+
21
+ /**
22
+ * Find the entry point for the application
23
+ */
24
+ async function findEntryPoint(projectRoot: string): Promise<string | null> {
25
+ const possibleEntries = [
26
+ 'server/main.ts',
27
+ 'src/main.ts',
28
+ 'src/index.ts',
29
+ 'main.ts',
30
+ 'index.ts',
31
+ 'server.ts',
32
+ 'app.ts',
33
+ ];
34
+
35
+ for (const entry of possibleEntries) {
36
+ const entryPath = joinPaths(projectRoot, entry);
37
+ if (await fileExists(entryPath)) {
38
+ return entry;
39
+ }
40
+ }
41
+
42
+ return null;
43
+ }
44
+
45
+ /**
46
+ * Check if package.json has dev script
47
+ */
48
+ async function hasDevScript(projectRoot: string): Promise<boolean> {
49
+ const packageJsonPath = joinPaths(projectRoot, 'package.json');
50
+ if (!(await fileExists(packageJsonPath))) {
51
+ return false;
52
+ }
53
+
54
+ try {
55
+ const content = await readFile(packageJsonPath);
56
+ const pkg = JSON.parse(content);
57
+ return !!pkg.scripts?.dev;
58
+ } catch {
59
+ return false;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Handle dev command
65
+ */
66
+ async function handleDev(args: ParsedArgs): Promise<void> {
67
+ // Get options
68
+ const port = getOption(args, 'port', {
69
+ name: 'port',
70
+ alias: 'p',
71
+ type: 'number',
72
+ default: 3000,
73
+ description: '',
74
+ });
75
+
76
+ const host = getOption<string>(args, 'host', {
77
+ name: 'host',
78
+ alias: 'H',
79
+ type: 'string',
80
+ default: 'localhost',
81
+ description: '',
82
+ });
83
+
84
+ const hmr = !hasFlag(args, 'no-hmr');
85
+ const watch = !hasFlag(args, 'no-watch');
86
+ const openBrowser = hasFlag(args, 'open') || hasFlag(args, 'o');
87
+ const configPath = getOption<string>(args, 'config', {
88
+ name: 'config',
89
+ alias: 'c',
90
+ type: 'string',
91
+ description: '',
92
+ });
93
+
94
+ // Check if in a Bueno project
95
+ const projectRoot = await getProjectRoot();
96
+ if (!projectRoot) {
97
+ throw new CLIError(
98
+ 'Not in a project directory. Run this command from a Bueno project.',
99
+ CLIErrorType.NOT_FOUND,
100
+ );
101
+ }
102
+
103
+ if (!(await isBuenoProject())) {
104
+ throw new CLIError(
105
+ 'Not a Bueno project. Make sure you have a bueno.config.ts or bueno in your dependencies.',
106
+ CLIErrorType.NOT_FOUND,
107
+ );
108
+ }
109
+
110
+ // Find entry point
111
+ const entryPoint = await findEntryPoint(projectRoot);
112
+ if (!entryPoint) {
113
+ throw new CLIError(
114
+ 'Could not find entry point. Make sure you have a main.ts or index.ts file.',
115
+ CLIErrorType.FILE_NOT_FOUND,
116
+ );
117
+ }
118
+
119
+ // Build the command
120
+ const bunArgs: string[] = [];
121
+
122
+ if (watch) {
123
+ bunArgs.push('--watch');
124
+ }
125
+
126
+ if (hmr) {
127
+ // HMR is handled by the dev server in the framework
128
+ cliConsole.debug('HMR enabled');
129
+ }
130
+
131
+ bunArgs.push(entryPoint);
132
+
133
+ // Set environment variables
134
+ const env: Record<string, string> = {
135
+ NODE_ENV: 'development',
136
+ PORT: String(port),
137
+ HOST: host,
138
+ };
139
+
140
+ if (configPath) {
141
+ env.BUENO_CONFIG = configPath;
142
+ }
143
+
144
+ // Display startup info
145
+ cliConsole.header('Starting Development Server');
146
+ cliConsole.log(`${colors.bold('Entry:')} ${entryPoint}`);
147
+ cliConsole.log(`${colors.bold('Port:')} ${port}`);
148
+ cliConsole.log(`${colors.bold('Host:')} ${host}`);
149
+ cliConsole.log(`${colors.bold('Watch:')} ${watch ? colors.green('enabled') : colors.red('disabled')}`);
150
+ cliConsole.log(`${colors.bold('HMR:')} ${hmr ? colors.green('enabled') : colors.red('disabled')}`);
151
+ cliConsole.log('');
152
+
153
+ // Start the server using Bun
154
+ const s = spinner('Starting development server...');
155
+
156
+ try {
157
+ // Use Bun's spawn to run the dev server
158
+ const proc = Bun.spawn(['bun', 'run', ...bunArgs], {
159
+ cwd: projectRoot,
160
+ env: { ...process.env, ...env },
161
+ stdout: 'inherit',
162
+ stderr: 'inherit',
163
+ });
164
+
165
+ s.success(`Development server running at ${colors.cyan(`http://${host}:${port}`)}`);
166
+
167
+ // Open browser if requested
168
+ if (openBrowser) {
169
+ const openCommand = process.platform === 'darwin'
170
+ ? 'open'
171
+ : process.platform === 'win32'
172
+ ? 'start'
173
+ : 'xdg-open';
174
+
175
+ Bun.spawn([openCommand, `http://${host}:${port}`], {
176
+ cwd: projectRoot,
177
+ });
178
+ }
179
+
180
+ // Wait for the process to exit
181
+ const exitCode = await proc.exited;
182
+
183
+ if (exitCode !== 0 && exitCode !== null) {
184
+ cliConsole.error(`Server exited with code ${exitCode}`);
185
+ process.exit(exitCode);
186
+ }
187
+ } catch (error) {
188
+ s.error();
189
+ throw error;
190
+ }
191
+ }
192
+
193
+ // Register the command
194
+ defineCommand(
195
+ {
196
+ name: 'dev',
197
+ description: 'Start the development server with hot reload',
198
+ options: [
199
+ {
200
+ name: 'port',
201
+ alias: 'p',
202
+ type: 'number',
203
+ default: 3000,
204
+ description: 'Server port',
205
+ },
206
+ {
207
+ name: 'host',
208
+ alias: 'H',
209
+ type: 'string',
210
+ default: 'localhost',
211
+ description: 'Server hostname',
212
+ },
213
+ {
214
+ name: 'no-hmr',
215
+ type: 'boolean',
216
+ default: false,
217
+ description: 'Disable hot module replacement',
218
+ },
219
+ {
220
+ name: 'no-watch',
221
+ type: 'boolean',
222
+ default: false,
223
+ description: 'Disable file watching',
224
+ },
225
+ {
226
+ name: 'open',
227
+ alias: 'o',
228
+ type: 'boolean',
229
+ default: false,
230
+ description: 'Open browser on start',
231
+ },
232
+ {
233
+ name: 'config',
234
+ alias: 'c',
235
+ type: 'string',
236
+ description: 'Path to config file',
237
+ },
238
+ ],
239
+ examples: [
240
+ 'bueno dev',
241
+ 'bueno dev --port 4000',
242
+ 'bueno dev --host 0.0.0.0',
243
+ 'bueno dev --no-hmr',
244
+ 'bueno dev --open',
245
+ ],
246
+ },
247
+ handleDev,
248
+ );