@buenojs/bueno 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/.env.example +109 -0
  2. package/.github/workflows/ci.yml +31 -0
  3. package/LICENSE +21 -0
  4. package/README.md +892 -0
  5. package/architecture.md +652 -0
  6. package/bun.lock +70 -0
  7. package/dist/cli/index.js +3233 -0
  8. package/dist/index.js +9014 -0
  9. package/package.json +77 -0
  10. package/src/cache/index.ts +795 -0
  11. package/src/cli/ARCHITECTURE.md +837 -0
  12. package/src/cli/bin.ts +10 -0
  13. package/src/cli/commands/build.ts +425 -0
  14. package/src/cli/commands/dev.ts +248 -0
  15. package/src/cli/commands/generate.ts +541 -0
  16. package/src/cli/commands/help.ts +55 -0
  17. package/src/cli/commands/index.ts +112 -0
  18. package/src/cli/commands/migration.ts +355 -0
  19. package/src/cli/commands/new.ts +804 -0
  20. package/src/cli/commands/start.ts +208 -0
  21. package/src/cli/core/args.ts +283 -0
  22. package/src/cli/core/console.ts +349 -0
  23. package/src/cli/core/index.ts +60 -0
  24. package/src/cli/core/prompt.ts +424 -0
  25. package/src/cli/core/spinner.ts +265 -0
  26. package/src/cli/index.ts +135 -0
  27. package/src/cli/templates/deploy.ts +295 -0
  28. package/src/cli/templates/docker.ts +307 -0
  29. package/src/cli/templates/index.ts +24 -0
  30. package/src/cli/utils/fs.ts +428 -0
  31. package/src/cli/utils/index.ts +8 -0
  32. package/src/cli/utils/strings.ts +197 -0
  33. package/src/config/env.ts +408 -0
  34. package/src/config/index.ts +506 -0
  35. package/src/config/loader.ts +329 -0
  36. package/src/config/merge.ts +285 -0
  37. package/src/config/types.ts +320 -0
  38. package/src/config/validation.ts +441 -0
  39. package/src/container/forward-ref.ts +143 -0
  40. package/src/container/index.ts +386 -0
  41. package/src/context/index.ts +360 -0
  42. package/src/database/index.ts +1142 -0
  43. package/src/database/migrations/index.ts +371 -0
  44. package/src/database/schema/index.ts +619 -0
  45. package/src/frontend/api-routes.ts +640 -0
  46. package/src/frontend/bundler.ts +643 -0
  47. package/src/frontend/console-client.ts +419 -0
  48. package/src/frontend/console-stream.ts +587 -0
  49. package/src/frontend/dev-server.ts +846 -0
  50. package/src/frontend/file-router.ts +611 -0
  51. package/src/frontend/frameworks/index.ts +106 -0
  52. package/src/frontend/frameworks/react.ts +85 -0
  53. package/src/frontend/frameworks/solid.ts +104 -0
  54. package/src/frontend/frameworks/svelte.ts +110 -0
  55. package/src/frontend/frameworks/vue.ts +92 -0
  56. package/src/frontend/hmr-client.ts +663 -0
  57. package/src/frontend/hmr.ts +728 -0
  58. package/src/frontend/index.ts +342 -0
  59. package/src/frontend/islands.ts +552 -0
  60. package/src/frontend/isr.ts +555 -0
  61. package/src/frontend/layout.ts +475 -0
  62. package/src/frontend/ssr/react.ts +446 -0
  63. package/src/frontend/ssr/solid.ts +523 -0
  64. package/src/frontend/ssr/svelte.ts +546 -0
  65. package/src/frontend/ssr/vue.ts +504 -0
  66. package/src/frontend/ssr.ts +699 -0
  67. package/src/frontend/types.ts +2274 -0
  68. package/src/health/index.ts +604 -0
  69. package/src/index.ts +410 -0
  70. package/src/lock/index.ts +587 -0
  71. package/src/logger/index.ts +444 -0
  72. package/src/logger/transports/index.ts +969 -0
  73. package/src/metrics/index.ts +494 -0
  74. package/src/middleware/built-in.ts +360 -0
  75. package/src/middleware/index.ts +94 -0
  76. package/src/modules/filters.ts +458 -0
  77. package/src/modules/guards.ts +405 -0
  78. package/src/modules/index.ts +1256 -0
  79. package/src/modules/interceptors.ts +574 -0
  80. package/src/modules/lazy.ts +418 -0
  81. package/src/modules/lifecycle.ts +478 -0
  82. package/src/modules/metadata.ts +90 -0
  83. package/src/modules/pipes.ts +626 -0
  84. package/src/router/index.ts +339 -0
  85. package/src/router/linear.ts +371 -0
  86. package/src/router/regex.ts +292 -0
  87. package/src/router/tree.ts +562 -0
  88. package/src/rpc/index.ts +1263 -0
  89. package/src/security/index.ts +436 -0
  90. package/src/ssg/index.ts +631 -0
  91. package/src/storage/index.ts +456 -0
  92. package/src/telemetry/index.ts +1097 -0
  93. package/src/testing/index.ts +1586 -0
  94. package/src/types/index.ts +236 -0
  95. package/src/types/optional-deps.d.ts +219 -0
  96. package/src/validation/index.ts +276 -0
  97. package/src/websocket/index.ts +1004 -0
  98. package/tests/integration/cli.test.ts +1016 -0
  99. package/tests/integration/fullstack.test.ts +234 -0
  100. package/tests/unit/cache.test.ts +174 -0
  101. package/tests/unit/cli-commands.test.ts +892 -0
  102. package/tests/unit/cli.test.ts +1258 -0
  103. package/tests/unit/container.test.ts +279 -0
  104. package/tests/unit/context.test.ts +221 -0
  105. package/tests/unit/database.test.ts +183 -0
  106. package/tests/unit/linear-router.test.ts +280 -0
  107. package/tests/unit/lock.test.ts +336 -0
  108. package/tests/unit/middleware.test.ts +184 -0
  109. package/tests/unit/modules.test.ts +142 -0
  110. package/tests/unit/pubsub.test.ts +257 -0
  111. package/tests/unit/regex-router.test.ts +265 -0
  112. package/tests/unit/router.test.ts +373 -0
  113. package/tests/unit/rpc.test.ts +1248 -0
  114. package/tests/unit/security.test.ts +174 -0
  115. package/tests/unit/telemetry.test.ts +371 -0
  116. package/tests/unit/test-cache.test.ts +110 -0
  117. package/tests/unit/test-database.test.ts +282 -0
  118. package/tests/unit/tree-router.test.ts +325 -0
  119. package/tests/unit/validation.test.ts +794 -0
  120. package/tsconfig.json +27 -0
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Start Command
3
+ *
4
+ * Start the production server
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
+ } from '../utils/fs';
17
+ import { CLIError, CLIErrorType } from '../index';
18
+
19
+ /**
20
+ * Find the entry point for the application
21
+ */
22
+ async function findEntryPoint(projectRoot: string): Promise<string | null> {
23
+ // Check for built files first
24
+ const possibleBuiltEntries = [
25
+ 'dist/index.js',
26
+ 'dist/main.js',
27
+ 'dist/server.js',
28
+ 'dist/app.js',
29
+ ];
30
+
31
+ for (const entry of possibleBuiltEntries) {
32
+ const entryPath = joinPaths(projectRoot, entry);
33
+ if (await fileExists(entryPath)) {
34
+ return entry;
35
+ }
36
+ }
37
+
38
+ // Fall back to source files
39
+ const possibleSourceEntries = [
40
+ 'server/main.ts',
41
+ 'src/main.ts',
42
+ 'src/index.ts',
43
+ 'main.ts',
44
+ 'index.ts',
45
+ 'server.ts',
46
+ 'app.ts',
47
+ ];
48
+
49
+ for (const entry of possibleSourceEntries) {
50
+ const entryPath = joinPaths(projectRoot, entry);
51
+ if (await fileExists(entryPath)) {
52
+ return entry;
53
+ }
54
+ }
55
+
56
+ return null;
57
+ }
58
+
59
+ /**
60
+ * Handle start command
61
+ */
62
+ async function handleStart(args: ParsedArgs): Promise<void> {
63
+ // Get options
64
+ const port = getOption(args, 'port', {
65
+ name: 'port',
66
+ alias: 'p',
67
+ type: 'number',
68
+ default: 3000,
69
+ description: '',
70
+ });
71
+
72
+ const host = getOption<string>(args, 'host', {
73
+ name: 'host',
74
+ alias: 'H',
75
+ type: 'string',
76
+ default: '0.0.0.0',
77
+ description: '',
78
+ });
79
+
80
+ const workers = getOption(args, 'workers', {
81
+ name: 'workers',
82
+ alias: 'w',
83
+ type: 'string',
84
+ default: 'auto',
85
+ description: '',
86
+ });
87
+
88
+ const configPath = getOption<string>(args, 'config', {
89
+ name: 'config',
90
+ alias: 'c',
91
+ type: 'string',
92
+ description: '',
93
+ });
94
+
95
+ // Check if in a Bueno project
96
+ const projectRoot = await getProjectRoot();
97
+ if (!projectRoot) {
98
+ throw new CLIError(
99
+ 'Not in a project directory. Run this command from a Bueno project.',
100
+ CLIErrorType.NOT_FOUND,
101
+ );
102
+ }
103
+
104
+ if (!(await isBuenoProject())) {
105
+ throw new CLIError(
106
+ 'Not a Bueno project. Make sure you have a bueno.config.ts or bueno in your dependencies.',
107
+ CLIErrorType.NOT_FOUND,
108
+ );
109
+ }
110
+
111
+ // Find entry point
112
+ const entryPoint = await findEntryPoint(projectRoot);
113
+ if (!entryPoint) {
114
+ throw new CLIError(
115
+ 'Could not find entry point. Make sure you have built the application or have a main.ts file.',
116
+ CLIErrorType.FILE_NOT_FOUND,
117
+ );
118
+ }
119
+
120
+ // Display startup info
121
+ cliConsole.header('Starting Production Server');
122
+ cliConsole.log(`${colors.bold('Entry:')} ${entryPoint}`);
123
+ cliConsole.log(`${colors.bold('Port:')} ${port}`);
124
+ cliConsole.log(`${colors.bold('Host:')} ${host}`);
125
+ cliConsole.log(`${colors.bold('Workers:')} ${workers}`);
126
+ cliConsole.log('');
127
+
128
+ // Set environment variables
129
+ const env: Record<string, string> = {
130
+ NODE_ENV: 'production',
131
+ PORT: String(port),
132
+ HOST: host,
133
+ };
134
+
135
+ if (configPath) {
136
+ env.BUENO_CONFIG = configPath;
137
+ }
138
+
139
+ // Start the server using Bun
140
+ const s = spinner('Starting production server...');
141
+
142
+ try {
143
+ // Use Bun's spawn to run the production server
144
+ const proc = Bun.spawn(['bun', 'run', entryPoint], {
145
+ cwd: projectRoot,
146
+ env: { ...process.env, ...env },
147
+ stdout: 'inherit',
148
+ stderr: 'inherit',
149
+ });
150
+
151
+ s.success(`Production server running at ${colors.cyan(`http://${host}:${port}`)}`);
152
+
153
+ // Wait for the process to exit
154
+ const exitCode = await proc.exited;
155
+
156
+ if (exitCode !== 0 && exitCode !== null) {
157
+ cliConsole.error(`Server exited with code ${exitCode}`);
158
+ process.exit(exitCode);
159
+ }
160
+ } catch (error) {
161
+ s.error();
162
+ throw error;
163
+ }
164
+ }
165
+
166
+ // Register the command
167
+ defineCommand(
168
+ {
169
+ name: 'start',
170
+ description: 'Start the production server',
171
+ options: [
172
+ {
173
+ name: 'port',
174
+ alias: 'p',
175
+ type: 'number',
176
+ default: 3000,
177
+ description: 'Server port',
178
+ },
179
+ {
180
+ name: 'host',
181
+ alias: 'H',
182
+ type: 'string',
183
+ default: '0.0.0.0',
184
+ description: 'Server hostname',
185
+ },
186
+ {
187
+ name: 'workers',
188
+ alias: 'w',
189
+ type: 'string',
190
+ default: 'auto',
191
+ description: 'Number of worker threads',
192
+ },
193
+ {
194
+ name: 'config',
195
+ alias: 'c',
196
+ type: 'string',
197
+ description: 'Path to config file',
198
+ },
199
+ ],
200
+ examples: [
201
+ 'bueno start',
202
+ 'bueno start --port 8080',
203
+ 'bueno start --host 0.0.0.0',
204
+ 'bueno start --workers 4',
205
+ ],
206
+ },
207
+ handleStart,
208
+ );
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Argument Parser for Bueno CLI
3
+ *
4
+ * Parses command line arguments using Bun's native process.argv
5
+ * Supports positional arguments, flags, and options
6
+ */
7
+
8
+ export interface ParsedArgs {
9
+ command: string;
10
+ positionals: string[];
11
+ options: Record<string, string | boolean | number>;
12
+ flags: Set<string>;
13
+ }
14
+
15
+ export interface OptionDefinition {
16
+ name: string;
17
+ alias?: string;
18
+ type: 'string' | 'boolean' | 'number';
19
+ default?: string | boolean | number;
20
+ description: string;
21
+ }
22
+
23
+ export interface CommandDefinition {
24
+ name: string;
25
+ alias?: string;
26
+ description: string;
27
+ options?: OptionDefinition[];
28
+ positionals?: { name: string; required: boolean; description: string }[];
29
+ examples?: string[];
30
+ }
31
+
32
+ /**
33
+ * Parse command line arguments
34
+ */
35
+ export function parseArgs(
36
+ argv: string[] = process.argv.slice(2),
37
+ ): ParsedArgs {
38
+ const result: ParsedArgs = {
39
+ command: '',
40
+ positionals: [],
41
+ options: {},
42
+ flags: new Set(),
43
+ };
44
+
45
+ for (let i = 0; i < argv.length; i++) {
46
+ const arg = argv[i];
47
+
48
+ if (!arg) continue;
49
+
50
+ // Long option: --option=value or --option value
51
+ if (arg.startsWith('--')) {
52
+ const eqIndex = arg.indexOf('=');
53
+ if (eqIndex !== -1) {
54
+ // --option=value
55
+ const name = arg.slice(2, eqIndex);
56
+ const value = arg.slice(eqIndex + 1);
57
+ result.options[name] = value;
58
+ } else {
59
+ // --option value or --flag
60
+ const name = arg.slice(2);
61
+ const nextArg = argv[i + 1];
62
+
63
+ // Check if it's a flag (no value or next arg starts with -)
64
+ if (!nextArg || nextArg.startsWith('-')) {
65
+ result.options[name] = true;
66
+ result.flags.add(name);
67
+ } else {
68
+ result.options[name] = nextArg;
69
+ i++; // Skip next arg as it's the value
70
+ }
71
+ }
72
+ }
73
+ // Short option: -o value or -abc (multiple flags)
74
+ else if (arg.startsWith('-') && arg.length > 1) {
75
+ const chars = arg.slice(1);
76
+
77
+ // Check if it's a combined flag like -abc
78
+ if (chars.length > 1) {
79
+ // Treat each char as a flag
80
+ for (const char of chars) {
81
+ result.options[char] = true;
82
+ result.flags.add(char);
83
+ }
84
+ } else {
85
+ // Single short option
86
+ const name = chars;
87
+ const nextArg = argv[i + 1];
88
+
89
+ if (!nextArg || nextArg.startsWith('-')) {
90
+ result.options[name] = true;
91
+ result.flags.add(name);
92
+ } else {
93
+ result.options[name] = nextArg;
94
+ i++; // Skip next arg as it's the value
95
+ }
96
+ }
97
+ }
98
+ // Positional argument
99
+ else {
100
+ if (!result.command) {
101
+ result.command = arg;
102
+ } else {
103
+ result.positionals.push(arg);
104
+ }
105
+ }
106
+ }
107
+
108
+ return result;
109
+ }
110
+
111
+ /**
112
+ * Get option value with type coercion and default
113
+ */
114
+ export function getOption<T extends string | boolean | number>(
115
+ parsed: ParsedArgs,
116
+ name: string,
117
+ definition: OptionDefinition,
118
+ ): T {
119
+ const value = parsed.options[name] ?? parsed.options[definition.alias ?? ''];
120
+
121
+ if (value === undefined) {
122
+ return definition.default as T;
123
+ }
124
+
125
+ if (definition.type === 'boolean') {
126
+ return (value === true || value === 'true') as T;
127
+ }
128
+
129
+ if (definition.type === 'number') {
130
+ return (typeof value === 'number' ? value : typeof value === 'string' ? parseInt(value, 10) : NaN) as T;
131
+ }
132
+
133
+ return value as T;
134
+ }
135
+
136
+ /**
137
+ * Check if a flag is set
138
+ */
139
+ export function hasFlag(parsed: ParsedArgs, name: string, alias?: string): boolean {
140
+ return parsed.flags.has(name) || (alias ? parsed.flags.has(alias) : false);
141
+ }
142
+
143
+ /**
144
+ * Check if an option is set (either as flag or with value)
145
+ */
146
+ export function hasOption(parsed: ParsedArgs, name: string, alias?: string): boolean {
147
+ return name in parsed.options || (alias ? alias in parsed.options : false);
148
+ }
149
+
150
+ /**
151
+ * Get all values for an option that can be specified multiple times
152
+ * Parses raw argv to collect all occurrences of the option
153
+ */
154
+ export function getOptionValues(parsed: ParsedArgs, name: string, alias?: string): string[] {
155
+ const values: string[] = [];
156
+ const argv = process.argv.slice(2);
157
+
158
+ for (let i = 0; i < argv.length; i++) {
159
+ const arg = argv[i];
160
+ if (!arg) continue;
161
+
162
+ // Long option: --name value or --name=value
163
+ if (arg === `--${name}`) {
164
+ const nextArg = argv[i + 1];
165
+ if (nextArg && !nextArg.startsWith('-')) {
166
+ values.push(nextArg);
167
+ i++; // Skip next arg
168
+ }
169
+ } else if (arg.startsWith(`--${name}=`)) {
170
+ const value = arg.slice(name.length + 3); // --name=
171
+ values.push(value);
172
+ }
173
+ // Short option: -n value
174
+ else if (alias && arg === `-${alias}`) {
175
+ const nextArg = argv[i + 1];
176
+ if (nextArg && !nextArg.startsWith('-')) {
177
+ values.push(nextArg);
178
+ i++; // Skip next arg
179
+ }
180
+ }
181
+ }
182
+
183
+ return values;
184
+ }
185
+
186
+ /**
187
+ * Generate help text from command definition
188
+ */
189
+ export function generateHelpText(
190
+ command: CommandDefinition,
191
+ cliName = 'bueno',
192
+ ): string {
193
+ const lines: string[] = [];
194
+
195
+ // Description
196
+ lines.push(`\n${command.description}\n`);
197
+
198
+ // Usage
199
+ lines.push('Usage:');
200
+ let usage = ` ${cliName} ${command.name}`;
201
+
202
+ if (command.positionals) {
203
+ for (const pos of command.positionals) {
204
+ usage += pos.required ? ` <${pos.name}>` : ` [${pos.name}]`;
205
+ }
206
+ }
207
+
208
+ usage += ' [options]';
209
+ lines.push(usage + '\n');
210
+
211
+ // Positionals
212
+ if (command.positionals && command.positionals.length > 0) {
213
+ lines.push('Arguments:');
214
+ for (const pos of command.positionals) {
215
+ const required = pos.required ? ' (required)' : '';
216
+ lines.push(` ${pos.name.padEnd(20)} ${pos.description}${required}`);
217
+ }
218
+ lines.push('');
219
+ }
220
+
221
+ // Options
222
+ if (command.options && command.options.length > 0) {
223
+ lines.push('Options:');
224
+ for (const opt of command.options) {
225
+ let flag = `--${opt.name}`;
226
+ if (opt.alias) {
227
+ flag = `-${opt.alias}, ${flag}`;
228
+ }
229
+
230
+ let defaultValue = '';
231
+ if (opt.default !== undefined) {
232
+ defaultValue = ` (default: ${opt.default})`;
233
+ }
234
+
235
+ lines.push(` ${flag.padEnd(20)} ${opt.description}${defaultValue}`);
236
+ }
237
+ lines.push('');
238
+ }
239
+
240
+ // Examples
241
+ if (command.examples && command.examples.length > 0) {
242
+ lines.push('Examples:');
243
+ for (const example of command.examples) {
244
+ lines.push(` ${example}`);
245
+ }
246
+ lines.push('');
247
+ }
248
+
249
+ return lines.join('\n');
250
+ }
251
+
252
+ /**
253
+ * Generate global help text
254
+ */
255
+ export function generateGlobalHelpText(
256
+ commands: CommandDefinition[],
257
+ cliName = 'bueno',
258
+ ): string {
259
+ const lines: string[] = [];
260
+
261
+ lines.push(`\n${cliName} - A Bun-Native Full-Stack Framework CLI\n`);
262
+ lines.push('Usage:');
263
+ lines.push(` ${cliName} <command> [options]\n`);
264
+
265
+ lines.push('Commands:');
266
+ for (const cmd of commands) {
267
+ const name = cmd.alias ? `${cmd.name} (${cmd.alias})` : cmd.name;
268
+ lines.push(` ${name.padEnd(20)} ${cmd.description}`);
269
+ }
270
+ lines.push('');
271
+
272
+ lines.push('Global Options:');
273
+ lines.push(' --help, -h Show help for command');
274
+ lines.push(' --version, -v Show CLI version');
275
+ lines.push(' --verbose Enable verbose output');
276
+ lines.push(' --quiet Suppress non-essential output');
277
+ lines.push(' --no-color Disable colored output');
278
+ lines.push('');
279
+
280
+ lines.push(`Run '${cliName} <command> --help' for more information about a command.\n`);
281
+
282
+ return lines.join('\n');
283
+ }