@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,355 @@
1
+ /**
2
+ * Migration Command
3
+ *
4
+ * Manage database migrations
5
+ */
6
+
7
+ import { defineCommand } from './index';
8
+ import { getOption, hasFlag, type ParsedArgs } from '../core/args';
9
+ import { cliConsole, colors, printTable } from '../core/console';
10
+ import { spinner } from '../core/spinner';
11
+ import {
12
+ fileExists,
13
+ readFile,
14
+ writeFile,
15
+ listFiles,
16
+ getProjectRoot,
17
+ isBuenoProject,
18
+ joinPaths,
19
+ } from '../utils/fs';
20
+ import { CLIError, CLIErrorType } from '../index';
21
+
22
+ /**
23
+ * Migration actions
24
+ */
25
+ type MigrationAction = 'create' | 'up' | 'down' | 'reset' | 'refresh' | 'status';
26
+
27
+ /**
28
+ * Generate migration ID
29
+ */
30
+ function generateMigrationId(): string {
31
+ const now = new Date();
32
+ const year = now.getFullYear();
33
+ const month = String(now.getMonth() + 1).padStart(2, '0');
34
+ const day = String(now.getDate()).padStart(2, '0');
35
+ const hour = String(now.getHours()).padStart(2, '0');
36
+ const minute = String(now.getMinutes()).padStart(2, '0');
37
+ const second = String(now.getSeconds()).padStart(2, '0');
38
+ return `${year}${month}${day}${hour}${minute}${second}`;
39
+ }
40
+
41
+ /**
42
+ * Get migrations directory
43
+ */
44
+ async function getMigrationsDir(): Promise<string> {
45
+ const projectRoot = await getProjectRoot();
46
+ if (!projectRoot) {
47
+ throw new CLIError(
48
+ 'Not in a project directory',
49
+ CLIErrorType.NOT_FOUND,
50
+ );
51
+ }
52
+
53
+ // Check common locations
54
+ const possibleDirs = [
55
+ joinPaths(projectRoot, 'server', 'database', 'migrations'),
56
+ joinPaths(projectRoot, 'database', 'migrations'),
57
+ joinPaths(projectRoot, 'migrations'),
58
+ ];
59
+
60
+ for (const dir of possibleDirs) {
61
+ if (await fileExists(dir)) {
62
+ return dir;
63
+ }
64
+ }
65
+
66
+ // Default to server/database/migrations
67
+ return possibleDirs[0] ?? '';
68
+ }
69
+
70
+ /**
71
+ * Get migration files
72
+ */
73
+ async function getMigrationFiles(dir: string): Promise<string[]> {
74
+ if (!await fileExists(dir)) {
75
+ return [];
76
+ }
77
+
78
+ const files = await listFiles(dir, {
79
+ recursive: false,
80
+ pattern: /\.ts$/,
81
+ });
82
+
83
+ return files.sort();
84
+ }
85
+
86
+ /**
87
+ * Parse migration info from filename
88
+ */
89
+ function parseMigrationFile(filename: string): { id: string; name: string } {
90
+ const match = filename.match(/^(\d+)_(.+)\.ts$/);
91
+ if (!match || !match[1] || !match[2]) {
92
+ return { id: filename, name: filename };
93
+ }
94
+ return { id: match[1], name: match[2] };
95
+ }
96
+
97
+ /**
98
+ * Create a new migration file
99
+ */
100
+ async function createMigration(name: string, dryRun: boolean): Promise<string> {
101
+ const migrationsDir = await getMigrationsDir();
102
+ const id = generateMigrationId();
103
+ const kebabName = name.toLowerCase().replace(/\s+/g, '-');
104
+ const fileName = `${id}_${kebabName}.ts`;
105
+ const filePath = joinPaths(migrationsDir, fileName);
106
+
107
+ const template = `import { createMigration, type MigrationRunner } from 'bueno';
108
+
109
+ export default createMigration('${id}', '${kebabName}')
110
+ .up(async (db: MigrationRunner) => {
111
+ // TODO: Add migration logic
112
+ // Example:
113
+ // await db.createTable({
114
+ // name: '${kebabName}',
115
+ // columns: [
116
+ // { name: 'id', type: 'uuid', primary: true },
117
+ // { name: 'created_at', type: 'timestamp', default: 'NOW()' },
118
+ // ],
119
+ // });
120
+ })
121
+ .down(async (db: MigrationRunner) => {
122
+ // TODO: Add rollback logic
123
+ // Example:
124
+ // await db.dropTable('${kebabName}');
125
+ });
126
+ `;
127
+
128
+ if (dryRun) {
129
+ cliConsole.log(`\n${colors.bold('File:')} ${filePath}`);
130
+ cliConsole.log(colors.bold('Content:'));
131
+ cliConsole.log(template);
132
+ cliConsole.log('');
133
+ return filePath;
134
+ }
135
+
136
+ await writeFile(filePath, template);
137
+ return filePath;
138
+ }
139
+
140
+ /**
141
+ * Show migration status
142
+ */
143
+ async function showStatus(): Promise<void> {
144
+ const migrationsDir = await getMigrationsDir();
145
+ const files = await getMigrationFiles(migrationsDir);
146
+
147
+ if (files.length === 0) {
148
+ cliConsole.info('No migrations found');
149
+ return;
150
+ }
151
+
152
+ cliConsole.header('Migration Status');
153
+
154
+ const rows = files.map((file) => {
155
+ const info = parseMigrationFile(file.split('/').pop() ?? '');
156
+ return [info.id, info.name, colors.yellow('Pending')];
157
+ });
158
+
159
+ printTable(['ID', 'Name', 'Status'], rows);
160
+ cliConsole.log('');
161
+ cliConsole.log(`Total: ${files.length} migration(s)`);
162
+ }
163
+
164
+ /**
165
+ * Handle migration command
166
+ */
167
+ async function handleMigration(args: ParsedArgs): Promise<void> {
168
+ // Get action
169
+ const action = args.positionals[0] as MigrationAction | undefined;
170
+ if (!action) {
171
+ throw new CLIError(
172
+ 'Action is required. Usage: bueno migration <action>',
173
+ CLIErrorType.INVALID_ARGS,
174
+ );
175
+ }
176
+
177
+ const validActions: MigrationAction[] = ['create', 'up', 'down', 'reset', 'refresh', 'status'];
178
+ if (!validActions.includes(action)) {
179
+ throw new CLIError(
180
+ `Unknown action: ${action}. Valid actions: ${validActions.join(', ')}`,
181
+ CLIErrorType.INVALID_ARGS,
182
+ );
183
+ }
184
+
185
+ // Get options
186
+ const dryRun = hasFlag(args, 'dry-run');
187
+ const steps = getOption(args, 'steps', {
188
+ name: 'steps',
189
+ alias: 'n',
190
+ type: 'number',
191
+ default: 1,
192
+ description: '',
193
+ });
194
+
195
+ // Check if in a Bueno project (except for create with dry-run)
196
+ if (action !== 'create' || !dryRun) {
197
+ if (!(await isBuenoProject())) {
198
+ throw new CLIError(
199
+ 'Not in a Bueno project directory. Run this command from a Bueno project.',
200
+ CLIErrorType.NOT_FOUND,
201
+ );
202
+ }
203
+ }
204
+
205
+ switch (action) {
206
+ case 'create': {
207
+ const name = args.positionals[1];
208
+ if (!name) {
209
+ throw new CLIError(
210
+ 'Migration name is required. Usage: bueno migration create <name>',
211
+ CLIErrorType.INVALID_ARGS,
212
+ );
213
+ }
214
+
215
+ const s = spinner(`Creating migration ${colors.cyan(name)}...`);
216
+ try {
217
+ const filePath = await createMigration(name, dryRun);
218
+ if (dryRun) {
219
+ s.info('Dry run complete');
220
+ } else {
221
+ s.success(`Created ${colors.green(filePath)}`);
222
+ }
223
+ } catch (error) {
224
+ s.error();
225
+ throw error;
226
+ }
227
+ break;
228
+ }
229
+
230
+ case 'up': {
231
+ cliConsole.info('Running pending migrations...');
232
+ cliConsole.log('');
233
+ cliConsole.warn(
234
+ 'Migration execution requires database connection. Use the MigrationRunner in your application code.',
235
+ );
236
+ cliConsole.log('');
237
+ cliConsole.log('Example:');
238
+ cliConsole.log(colors.cyan(`
239
+ import { createMigrationRunner, loadMigrations } from 'bueno';
240
+ import { db } from './database';
241
+
242
+ const runner = createMigrationRunner(db);
243
+ const migrations = await loadMigrations('./database/migrations');
244
+ await runner.migrate(migrations);
245
+ `));
246
+ break;
247
+ }
248
+
249
+ case 'down': {
250
+ cliConsole.info(`Rolling back ${steps} migration(s)...`);
251
+ cliConsole.log('');
252
+ cliConsole.warn(
253
+ 'Migration rollback requires database connection. Use the MigrationRunner in your application code.',
254
+ );
255
+ cliConsole.log('');
256
+ cliConsole.log('Example:');
257
+ cliConsole.log(colors.cyan(`
258
+ import { createMigrationRunner, loadMigrations } from 'bueno';
259
+ import { db } from './database';
260
+
261
+ const runner = createMigrationRunner(db);
262
+ const migrations = await loadMigrations('./database/migrations');
263
+ await runner.rollback(migrations, ${steps});
264
+ `));
265
+ break;
266
+ }
267
+
268
+ case 'reset': {
269
+ cliConsole.info('Rolling back all migrations...');
270
+ cliConsole.log('');
271
+ cliConsole.warn(
272
+ 'Migration reset requires database connection. Use the MigrationRunner in your application code.',
273
+ );
274
+ cliConsole.log('');
275
+ cliConsole.log('Example:');
276
+ cliConsole.log(colors.cyan(`
277
+ import { createMigrationRunner, loadMigrations } from 'bueno';
278
+ import { db } from './database';
279
+
280
+ const runner = createMigrationRunner(db);
281
+ const migrations = await loadMigrations('./database/migrations');
282
+ await runner.reset(migrations);
283
+ `));
284
+ break;
285
+ }
286
+
287
+ case 'refresh': {
288
+ cliConsole.info('Refreshing all migrations...');
289
+ cliConsole.log('');
290
+ cliConsole.warn(
291
+ 'Migration refresh requires database connection. Use the MigrationRunner in your application code.',
292
+ );
293
+ cliConsole.log('');
294
+ cliConsole.log('Example:');
295
+ cliConsole.log(colors.cyan(`
296
+ import { createMigrationRunner, loadMigrations } from 'bueno';
297
+ import { db } from './database';
298
+
299
+ const runner = createMigrationRunner(db);
300
+ const migrations = await loadMigrations('./database/migrations');
301
+ await runner.refresh(migrations);
302
+ `));
303
+ break;
304
+ }
305
+
306
+ case 'status': {
307
+ await showStatus();
308
+ break;
309
+ }
310
+ }
311
+ }
312
+
313
+ // Register the command
314
+ defineCommand(
315
+ {
316
+ name: 'migration',
317
+ description: 'Manage database migrations',
318
+ positionals: [
319
+ {
320
+ name: 'action',
321
+ required: true,
322
+ description: 'Action to perform (create, up, down, reset, refresh, status)',
323
+ },
324
+ {
325
+ name: 'name',
326
+ required: false,
327
+ description: 'Migration name (required for create action)',
328
+ },
329
+ ],
330
+ options: [
331
+ {
332
+ name: 'steps',
333
+ alias: 'n',
334
+ type: 'number',
335
+ default: 1,
336
+ description: 'Number of migrations to rollback',
337
+ },
338
+ {
339
+ name: 'dry-run',
340
+ type: 'boolean',
341
+ default: false,
342
+ description: 'Show what would happen without executing',
343
+ },
344
+ ],
345
+ examples: [
346
+ 'bueno migration create add-users-table',
347
+ 'bueno migration up',
348
+ 'bueno migration down --steps 3',
349
+ 'bueno migration reset',
350
+ 'bueno migration refresh',
351
+ 'bueno migration status',
352
+ ],
353
+ },
354
+ handleMigration,
355
+ );