@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.
- package/.env.example +109 -0
- package/.github/workflows/ci.yml +31 -0
- package/LICENSE +21 -0
- package/README.md +892 -0
- package/architecture.md +652 -0
- package/bun.lock +70 -0
- package/dist/cli/index.js +3233 -0
- package/dist/index.js +9014 -0
- package/package.json +77 -0
- package/src/cache/index.ts +795 -0
- package/src/cli/ARCHITECTURE.md +837 -0
- package/src/cli/bin.ts +10 -0
- package/src/cli/commands/build.ts +425 -0
- package/src/cli/commands/dev.ts +248 -0
- package/src/cli/commands/generate.ts +541 -0
- package/src/cli/commands/help.ts +55 -0
- package/src/cli/commands/index.ts +112 -0
- package/src/cli/commands/migration.ts +355 -0
- package/src/cli/commands/new.ts +804 -0
- package/src/cli/commands/start.ts +208 -0
- package/src/cli/core/args.ts +283 -0
- package/src/cli/core/console.ts +349 -0
- package/src/cli/core/index.ts +60 -0
- package/src/cli/core/prompt.ts +424 -0
- package/src/cli/core/spinner.ts +265 -0
- package/src/cli/index.ts +135 -0
- package/src/cli/templates/deploy.ts +295 -0
- package/src/cli/templates/docker.ts +307 -0
- package/src/cli/templates/index.ts +24 -0
- package/src/cli/utils/fs.ts +428 -0
- package/src/cli/utils/index.ts +8 -0
- package/src/cli/utils/strings.ts +197 -0
- package/src/config/env.ts +408 -0
- package/src/config/index.ts +506 -0
- package/src/config/loader.ts +329 -0
- package/src/config/merge.ts +285 -0
- package/src/config/types.ts +320 -0
- package/src/config/validation.ts +441 -0
- package/src/container/forward-ref.ts +143 -0
- package/src/container/index.ts +386 -0
- package/src/context/index.ts +360 -0
- package/src/database/index.ts +1142 -0
- package/src/database/migrations/index.ts +371 -0
- package/src/database/schema/index.ts +619 -0
- package/src/frontend/api-routes.ts +640 -0
- package/src/frontend/bundler.ts +643 -0
- package/src/frontend/console-client.ts +419 -0
- package/src/frontend/console-stream.ts +587 -0
- package/src/frontend/dev-server.ts +846 -0
- package/src/frontend/file-router.ts +611 -0
- package/src/frontend/frameworks/index.ts +106 -0
- package/src/frontend/frameworks/react.ts +85 -0
- package/src/frontend/frameworks/solid.ts +104 -0
- package/src/frontend/frameworks/svelte.ts +110 -0
- package/src/frontend/frameworks/vue.ts +92 -0
- package/src/frontend/hmr-client.ts +663 -0
- package/src/frontend/hmr.ts +728 -0
- package/src/frontend/index.ts +342 -0
- package/src/frontend/islands.ts +552 -0
- package/src/frontend/isr.ts +555 -0
- package/src/frontend/layout.ts +475 -0
- package/src/frontend/ssr/react.ts +446 -0
- package/src/frontend/ssr/solid.ts +523 -0
- package/src/frontend/ssr/svelte.ts +546 -0
- package/src/frontend/ssr/vue.ts +504 -0
- package/src/frontend/ssr.ts +699 -0
- package/src/frontend/types.ts +2274 -0
- package/src/health/index.ts +604 -0
- package/src/index.ts +410 -0
- package/src/lock/index.ts +587 -0
- package/src/logger/index.ts +444 -0
- package/src/logger/transports/index.ts +969 -0
- package/src/metrics/index.ts +494 -0
- package/src/middleware/built-in.ts +360 -0
- package/src/middleware/index.ts +94 -0
- package/src/modules/filters.ts +458 -0
- package/src/modules/guards.ts +405 -0
- package/src/modules/index.ts +1256 -0
- package/src/modules/interceptors.ts +574 -0
- package/src/modules/lazy.ts +418 -0
- package/src/modules/lifecycle.ts +478 -0
- package/src/modules/metadata.ts +90 -0
- package/src/modules/pipes.ts +626 -0
- package/src/router/index.ts +339 -0
- package/src/router/linear.ts +371 -0
- package/src/router/regex.ts +292 -0
- package/src/router/tree.ts +562 -0
- package/src/rpc/index.ts +1263 -0
- package/src/security/index.ts +436 -0
- package/src/ssg/index.ts +631 -0
- package/src/storage/index.ts +456 -0
- package/src/telemetry/index.ts +1097 -0
- package/src/testing/index.ts +1586 -0
- package/src/types/index.ts +236 -0
- package/src/types/optional-deps.d.ts +219 -0
- package/src/validation/index.ts +276 -0
- package/src/websocket/index.ts +1004 -0
- package/tests/integration/cli.test.ts +1016 -0
- package/tests/integration/fullstack.test.ts +234 -0
- package/tests/unit/cache.test.ts +174 -0
- package/tests/unit/cli-commands.test.ts +892 -0
- package/tests/unit/cli.test.ts +1258 -0
- package/tests/unit/container.test.ts +279 -0
- package/tests/unit/context.test.ts +221 -0
- package/tests/unit/database.test.ts +183 -0
- package/tests/unit/linear-router.test.ts +280 -0
- package/tests/unit/lock.test.ts +336 -0
- package/tests/unit/middleware.test.ts +184 -0
- package/tests/unit/modules.test.ts +142 -0
- package/tests/unit/pubsub.test.ts +257 -0
- package/tests/unit/regex-router.test.ts +265 -0
- package/tests/unit/router.test.ts +373 -0
- package/tests/unit/rpc.test.ts +1248 -0
- package/tests/unit/security.test.ts +174 -0
- package/tests/unit/telemetry.test.ts +371 -0
- package/tests/unit/test-cache.test.ts +110 -0
- package/tests/unit/test-database.test.ts +282 -0
- package/tests/unit/tree-router.test.ts +325 -0
- package/tests/unit/validation.test.ts +794 -0
- 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
|
+
);
|