@codebakers/cli 1.3.0 → 1.4.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.
@@ -0,0 +1,9 @@
1
+ interface GenerateOptions {
2
+ type?: string;
3
+ name?: string;
4
+ }
5
+ /**
6
+ * Generate code from templates
7
+ */
8
+ export declare function generate(options: GenerateOptions): Promise<void>;
9
+ export {};
@@ -0,0 +1,653 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generate = generate;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const ora_1 = __importDefault(require("ora"));
9
+ const fs_1 = require("fs");
10
+ const path_1 = require("path");
11
+ const readline_1 = require("readline");
12
+ async function prompt(question) {
13
+ const rl = (0, readline_1.createInterface)({
14
+ input: process.stdin,
15
+ output: process.stdout,
16
+ });
17
+ return new Promise((resolve) => {
18
+ rl.question(question, (answer) => {
19
+ rl.close();
20
+ resolve(answer.trim());
21
+ });
22
+ });
23
+ }
24
+ // Convert to PascalCase
25
+ function toPascalCase(str) {
26
+ return str
27
+ .split(/[-_\s]+/)
28
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
29
+ .join('');
30
+ }
31
+ // Convert to kebab-case
32
+ function toKebabCase(str) {
33
+ return str
34
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
35
+ .replace(/[\s_]+/g, '-')
36
+ .toLowerCase();
37
+ }
38
+ // Convert to camelCase
39
+ function toCamelCase(str) {
40
+ const pascal = toPascalCase(str);
41
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
42
+ }
43
+ // Component template
44
+ function componentTemplate(name, hasProps) {
45
+ const pascalName = toPascalCase(name);
46
+ if (hasProps) {
47
+ return `import { cn } from '@/lib/utils';
48
+
49
+ interface ${pascalName}Props {
50
+ className?: string;
51
+ children?: React.ReactNode;
52
+ }
53
+
54
+ export function ${pascalName}({ className, children }: ${pascalName}Props) {
55
+ return (
56
+ <div className={cn('', className)}>
57
+ {children}
58
+ </div>
59
+ );
60
+ }
61
+ `;
62
+ }
63
+ return `import { cn } from '@/lib/utils';
64
+
65
+ export function ${pascalName}() {
66
+ return (
67
+ <div>
68
+ {/* ${pascalName} content */}
69
+ </div>
70
+ );
71
+ }
72
+ `;
73
+ }
74
+ // API route template
75
+ function apiRouteTemplate(name, methods) {
76
+ const lines = [
77
+ `import { NextRequest, NextResponse } from 'next/server';`,
78
+ `import { z } from 'zod';`,
79
+ `import { db } from '@/db';`,
80
+ ``,
81
+ ];
82
+ if (methods.includes('GET')) {
83
+ lines.push(`export async function GET(request: NextRequest) {
84
+ try {
85
+ // TODO: Implement GET logic
86
+ return NextResponse.json({ message: 'Success' });
87
+ } catch (error) {
88
+ console.error('GET /${name} error:', error);
89
+ return NextResponse.json(
90
+ { error: 'Internal server error' },
91
+ { status: 500 }
92
+ );
93
+ }
94
+ }
95
+ `);
96
+ }
97
+ if (methods.includes('POST')) {
98
+ const schemaName = `Create${toPascalCase(name)}Schema`;
99
+ lines.push(`const ${schemaName} = z.object({
100
+ // TODO: Define your schema
101
+ name: z.string().min(1),
102
+ });
103
+
104
+ export async function POST(request: NextRequest) {
105
+ try {
106
+ const body = await request.json();
107
+ const validated = ${schemaName}.parse(body);
108
+
109
+ // TODO: Implement POST logic
110
+ return NextResponse.json({ message: 'Created', data: validated });
111
+ } catch (error) {
112
+ if (error instanceof z.ZodError) {
113
+ return NextResponse.json(
114
+ { error: 'Validation failed', details: error.errors },
115
+ { status: 400 }
116
+ );
117
+ }
118
+ console.error('POST /${name} error:', error);
119
+ return NextResponse.json(
120
+ { error: 'Internal server error' },
121
+ { status: 500 }
122
+ );
123
+ }
124
+ }
125
+ `);
126
+ }
127
+ if (methods.includes('PUT')) {
128
+ const schemaName = `Update${toPascalCase(name)}Schema`;
129
+ lines.push(`const ${schemaName} = z.object({
130
+ // TODO: Define your update schema
131
+ id: z.string().uuid(),
132
+ name: z.string().min(1).optional(),
133
+ });
134
+
135
+ export async function PUT(request: NextRequest) {
136
+ try {
137
+ const body = await request.json();
138
+ const validated = ${schemaName}.parse(body);
139
+
140
+ // TODO: Implement PUT logic
141
+ return NextResponse.json({ message: 'Updated', data: validated });
142
+ } catch (error) {
143
+ if (error instanceof z.ZodError) {
144
+ return NextResponse.json(
145
+ { error: 'Validation failed', details: error.errors },
146
+ { status: 400 }
147
+ );
148
+ }
149
+ console.error('PUT /${name} error:', error);
150
+ return NextResponse.json(
151
+ { error: 'Internal server error' },
152
+ { status: 500 }
153
+ );
154
+ }
155
+ }
156
+ `);
157
+ }
158
+ if (methods.includes('DELETE')) {
159
+ lines.push(`export async function DELETE(request: NextRequest) {
160
+ try {
161
+ const { searchParams } = new URL(request.url);
162
+ const id = searchParams.get('id');
163
+
164
+ if (!id) {
165
+ return NextResponse.json(
166
+ { error: 'ID is required' },
167
+ { status: 400 }
168
+ );
169
+ }
170
+
171
+ // TODO: Implement DELETE logic
172
+ return NextResponse.json({ message: 'Deleted' });
173
+ } catch (error) {
174
+ console.error('DELETE /${name} error:', error);
175
+ return NextResponse.json(
176
+ { error: 'Internal server error' },
177
+ { status: 500 }
178
+ );
179
+ }
180
+ }
181
+ `);
182
+ }
183
+ return lines.join('\n');
184
+ }
185
+ // Service template
186
+ function serviceTemplate(name) {
187
+ const pascalName = toPascalCase(name);
188
+ const camelName = toCamelCase(name);
189
+ return `import { db } from '@/db';
190
+ import { z } from 'zod';
191
+
192
+ // Validation schemas
193
+ export const Create${pascalName}Schema = z.object({
194
+ // TODO: Define your schema
195
+ name: z.string().min(1),
196
+ });
197
+
198
+ export const Update${pascalName}Schema = Create${pascalName}Schema.partial();
199
+
200
+ export type Create${pascalName}Input = z.infer<typeof Create${pascalName}Schema>;
201
+ export type Update${pascalName}Input = z.infer<typeof Update${pascalName}Schema>;
202
+
203
+ /**
204
+ * ${pascalName} Service
205
+ * Handles all ${camelName}-related business logic
206
+ */
207
+ export const ${camelName}Service = {
208
+ /**
209
+ * Get all ${camelName}s
210
+ */
211
+ async getAll() {
212
+ // TODO: Implement
213
+ return [];
214
+ },
215
+
216
+ /**
217
+ * Get a single ${camelName} by ID
218
+ */
219
+ async getById(id: string) {
220
+ // TODO: Implement
221
+ return null;
222
+ },
223
+
224
+ /**
225
+ * Create a new ${camelName}
226
+ */
227
+ async create(input: Create${pascalName}Input) {
228
+ const validated = Create${pascalName}Schema.parse(input);
229
+ // TODO: Implement
230
+ return validated;
231
+ },
232
+
233
+ /**
234
+ * Update an existing ${camelName}
235
+ */
236
+ async update(id: string, input: Update${pascalName}Input) {
237
+ const validated = Update${pascalName}Schema.parse(input);
238
+ // TODO: Implement
239
+ return { id, ...validated };
240
+ },
241
+
242
+ /**
243
+ * Delete a ${camelName}
244
+ */
245
+ async delete(id: string) {
246
+ // TODO: Implement
247
+ return true;
248
+ },
249
+ };
250
+ `;
251
+ }
252
+ // Hook template
253
+ function hookTemplate(name) {
254
+ const hookName = name.startsWith('use') ? name : `use${toPascalCase(name)}`;
255
+ const pascalName = toPascalCase(name.replace(/^use/i, ''));
256
+ return `'use client';
257
+
258
+ import { useState, useEffect, useCallback } from 'react';
259
+
260
+ interface ${pascalName}State {
261
+ data: unknown | null;
262
+ isLoading: boolean;
263
+ error: Error | null;
264
+ }
265
+
266
+ interface ${hookName}Options {
267
+ // TODO: Add options if needed
268
+ }
269
+
270
+ export function ${hookName}(options?: ${hookName}Options) {
271
+ const [state, setState] = useState<${pascalName}State>({
272
+ data: null,
273
+ isLoading: false,
274
+ error: null,
275
+ });
276
+
277
+ const fetch${pascalName} = useCallback(async () => {
278
+ setState(prev => ({ ...prev, isLoading: true, error: null }));
279
+
280
+ try {
281
+ // TODO: Implement fetch logic
282
+ const data = null;
283
+ setState({ data, isLoading: false, error: null });
284
+ } catch (error) {
285
+ setState(prev => ({
286
+ ...prev,
287
+ isLoading: false,
288
+ error: error instanceof Error ? error : new Error('Unknown error'),
289
+ }));
290
+ }
291
+ }, []);
292
+
293
+ useEffect(() => {
294
+ fetch${pascalName}();
295
+ }, [fetch${pascalName}]);
296
+
297
+ return {
298
+ ...state,
299
+ refetch: fetch${pascalName},
300
+ };
301
+ }
302
+ `;
303
+ }
304
+ // Page template
305
+ function pageTemplate(name, isServer) {
306
+ const pascalName = toPascalCase(name);
307
+ if (isServer) {
308
+ return `import { Suspense } from 'react';
309
+
310
+ export const metadata = {
311
+ title: '${pascalName}',
312
+ description: '${pascalName} page',
313
+ };
314
+
315
+ async function ${pascalName}Content() {
316
+ // TODO: Fetch data server-side
317
+ const data = null;
318
+
319
+ return (
320
+ <div>
321
+ <h1 className="text-2xl font-bold">${pascalName}</h1>
322
+ {/* Content here */}
323
+ </div>
324
+ );
325
+ }
326
+
327
+ export default function ${pascalName}Page() {
328
+ return (
329
+ <main className="container mx-auto py-8">
330
+ <Suspense fallback={<div>Loading...</div>}>
331
+ <${pascalName}Content />
332
+ </Suspense>
333
+ </main>
334
+ );
335
+ }
336
+ `;
337
+ }
338
+ return `'use client';
339
+
340
+ import { useState } from 'react';
341
+
342
+ export default function ${pascalName}Page() {
343
+ const [loading, setLoading] = useState(false);
344
+
345
+ return (
346
+ <main className="container mx-auto py-8">
347
+ <h1 className="text-2xl font-bold">${pascalName}</h1>
348
+ {/* Content here */}
349
+ </main>
350
+ );
351
+ }
352
+ `;
353
+ }
354
+ // Schema template for Drizzle
355
+ function schemaTemplate(name) {
356
+ const tableName = toKebabCase(name).replace(/-/g, '_');
357
+ const pascalName = toPascalCase(name);
358
+ return `import { pgTable, text, timestamp, uuid, boolean } from 'drizzle-orm/pg-core';
359
+
360
+ export const ${tableName} = pgTable('${tableName}', {
361
+ id: uuid('id').primaryKey().defaultRandom(),
362
+ // TODO: Add your columns
363
+ name: text('name').notNull(),
364
+ description: text('description'),
365
+ isActive: boolean('is_active').default(true),
366
+ createdAt: timestamp('created_at').defaultNow().notNull(),
367
+ updatedAt: timestamp('updated_at').defaultNow().notNull(),
368
+ });
369
+
370
+ export type ${pascalName} = typeof ${tableName}.$inferSelect;
371
+ export type New${pascalName} = typeof ${tableName}.$inferInsert;
372
+ `;
373
+ }
374
+ // Form template
375
+ function formTemplate(name) {
376
+ const pascalName = toPascalCase(name);
377
+ const camelName = toCamelCase(name);
378
+ return `'use client';
379
+
380
+ import { useForm } from 'react-hook-form';
381
+ import { zodResolver } from '@hookform/resolvers/zod';
382
+ import { z } from 'zod';
383
+
384
+ const ${camelName}Schema = z.object({
385
+ // TODO: Define your form fields
386
+ name: z.string().min(1, 'Name is required'),
387
+ email: z.string().email('Invalid email'),
388
+ });
389
+
390
+ type ${pascalName}FormData = z.infer<typeof ${camelName}Schema>;
391
+
392
+ interface ${pascalName}FormProps {
393
+ onSubmit: (data: ${pascalName}FormData) => void | Promise<void>;
394
+ defaultValues?: Partial<${pascalName}FormData>;
395
+ isLoading?: boolean;
396
+ }
397
+
398
+ export function ${pascalName}Form({ onSubmit, defaultValues, isLoading }: ${pascalName}FormProps) {
399
+ const {
400
+ register,
401
+ handleSubmit,
402
+ formState: { errors },
403
+ } = useForm<${pascalName}FormData>({
404
+ resolver: zodResolver(${camelName}Schema),
405
+ defaultValues,
406
+ });
407
+
408
+ return (
409
+ <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
410
+ <div>
411
+ <label htmlFor="name" className="block text-sm font-medium">
412
+ Name
413
+ </label>
414
+ <input
415
+ {...register('name')}
416
+ type="text"
417
+ id="name"
418
+ className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2"
419
+ disabled={isLoading}
420
+ />
421
+ {errors.name && (
422
+ <p className="mt-1 text-sm text-red-600">{errors.name.message}</p>
423
+ )}
424
+ </div>
425
+
426
+ <div>
427
+ <label htmlFor="email" className="block text-sm font-medium">
428
+ Email
429
+ </label>
430
+ <input
431
+ {...register('email')}
432
+ type="email"
433
+ id="email"
434
+ className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2"
435
+ disabled={isLoading}
436
+ />
437
+ {errors.email && (
438
+ <p className="mt-1 text-sm text-red-600">{errors.email.message}</p>
439
+ )}
440
+ </div>
441
+
442
+ <button
443
+ type="submit"
444
+ disabled={isLoading}
445
+ className="rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"
446
+ >
447
+ {isLoading ? 'Submitting...' : 'Submit'}
448
+ </button>
449
+ </form>
450
+ );
451
+ }
452
+ `;
453
+ }
454
+ /**
455
+ * Generate code from templates
456
+ */
457
+ async function generate(options) {
458
+ console.log(chalk_1.default.blue(`
459
+ ╔═══════════════════════════════════════════════════════════╗
460
+ ║ ║
461
+ ║ ${chalk_1.default.bold('CodeBakers Code Generator')} ║
462
+ ║ ║
463
+ ║ Generate production-ready code from templates ║
464
+ ║ ║
465
+ ╚═══════════════════════════════════════════════════════════╝
466
+ `));
467
+ const cwd = process.cwd();
468
+ // Check if we're in a project
469
+ const hasPackageJson = (0, fs_1.existsSync)((0, path_1.join)(cwd, 'package.json'));
470
+ if (!hasPackageJson) {
471
+ console.log(chalk_1.default.yellow(' No package.json found. Run this in a project directory.\n'));
472
+ console.log(chalk_1.default.gray(' Use `codebakers scaffold` to create a new project first.\n'));
473
+ return;
474
+ }
475
+ // Available generators
476
+ const generators = [
477
+ { key: '1', name: 'component', desc: 'React component with TypeScript' },
478
+ { key: '2', name: 'api', desc: 'Next.js API route with validation' },
479
+ { key: '3', name: 'service', desc: 'Business logic service with CRUD' },
480
+ { key: '4', name: 'hook', desc: 'React hook with state management' },
481
+ { key: '5', name: 'page', desc: 'Next.js page (server or client)' },
482
+ { key: '6', name: 'schema', desc: 'Drizzle database table schema' },
483
+ { key: '7', name: 'form', desc: 'Form with React Hook Form + Zod' },
484
+ ];
485
+ let type = options.type;
486
+ let name = options.name;
487
+ // If type not provided, ask
488
+ if (!type) {
489
+ console.log(chalk_1.default.white(' What would you like to generate?\n'));
490
+ for (const gen of generators) {
491
+ console.log(chalk_1.default.gray(` ${gen.key}. `) + chalk_1.default.cyan(gen.name) + chalk_1.default.gray(` - ${gen.desc}`));
492
+ }
493
+ console.log('');
494
+ let choice = '';
495
+ while (!['1', '2', '3', '4', '5', '6', '7'].includes(choice)) {
496
+ choice = await prompt(' Enter 1-7: ');
497
+ }
498
+ type = generators.find(g => g.key === choice)?.name;
499
+ }
500
+ // If name not provided, ask
501
+ if (!name) {
502
+ name = await prompt(` ${toPascalCase(type)} name: `);
503
+ if (!name) {
504
+ console.log(chalk_1.default.red('\n Name is required.\n'));
505
+ return;
506
+ }
507
+ }
508
+ const spinner = (0, ora_1.default)(' Generating...').start();
509
+ try {
510
+ let filePath;
511
+ let content;
512
+ switch (type) {
513
+ case 'component': {
514
+ const hasProps = await prompt(' Include props interface? (Y/n): ');
515
+ content = componentTemplate(name, hasProps.toLowerCase() !== 'n');
516
+ const componentDir = (0, path_1.join)(cwd, 'src/components');
517
+ if (!(0, fs_1.existsSync)(componentDir)) {
518
+ (0, fs_1.mkdirSync)(componentDir, { recursive: true });
519
+ }
520
+ filePath = (0, path_1.join)(componentDir, `${toPascalCase(name)}.tsx`);
521
+ break;
522
+ }
523
+ case 'api': {
524
+ console.log(chalk_1.default.gray('\n Select HTTP methods (comma-separated):'));
525
+ console.log(chalk_1.default.gray(' Examples: GET,POST or GET,POST,PUT,DELETE\n'));
526
+ const methodsInput = await prompt(' Methods (GET,POST): ') || 'GET,POST';
527
+ const methods = methodsInput.toUpperCase().split(',').map(m => m.trim());
528
+ content = apiRouteTemplate(name, methods);
529
+ const apiDir = (0, path_1.join)(cwd, 'src/app/api', toKebabCase(name));
530
+ if (!(0, fs_1.existsSync)(apiDir)) {
531
+ (0, fs_1.mkdirSync)(apiDir, { recursive: true });
532
+ }
533
+ filePath = (0, path_1.join)(apiDir, 'route.ts');
534
+ break;
535
+ }
536
+ case 'service': {
537
+ content = serviceTemplate(name);
538
+ const serviceDir = (0, path_1.join)(cwd, 'src/services');
539
+ if (!(0, fs_1.existsSync)(serviceDir)) {
540
+ (0, fs_1.mkdirSync)(serviceDir, { recursive: true });
541
+ }
542
+ filePath = (0, path_1.join)(serviceDir, `${toKebabCase(name)}.ts`);
543
+ break;
544
+ }
545
+ case 'hook': {
546
+ content = hookTemplate(name);
547
+ const hookDir = (0, path_1.join)(cwd, 'src/hooks');
548
+ if (!(0, fs_1.existsSync)(hookDir)) {
549
+ (0, fs_1.mkdirSync)(hookDir, { recursive: true });
550
+ }
551
+ const hookName = name.startsWith('use') ? name : `use${toPascalCase(name)}`;
552
+ filePath = (0, path_1.join)(hookDir, `${hookName}.ts`);
553
+ break;
554
+ }
555
+ case 'page': {
556
+ const isServer = await prompt(' Server component? (Y/n): ');
557
+ content = pageTemplate(name, isServer.toLowerCase() !== 'n');
558
+ const pageDir = (0, path_1.join)(cwd, 'src/app', toKebabCase(name));
559
+ if (!(0, fs_1.existsSync)(pageDir)) {
560
+ (0, fs_1.mkdirSync)(pageDir, { recursive: true });
561
+ }
562
+ filePath = (0, path_1.join)(pageDir, 'page.tsx');
563
+ break;
564
+ }
565
+ case 'schema': {
566
+ content = schemaTemplate(name);
567
+ const schemaDir = (0, path_1.join)(cwd, 'src/db/schemas');
568
+ if (!(0, fs_1.existsSync)(schemaDir)) {
569
+ (0, fs_1.mkdirSync)(schemaDir, { recursive: true });
570
+ }
571
+ filePath = (0, path_1.join)(schemaDir, `${toKebabCase(name)}.ts`);
572
+ break;
573
+ }
574
+ case 'form': {
575
+ content = formTemplate(name);
576
+ const formDir = (0, path_1.join)(cwd, 'src/components/forms');
577
+ if (!(0, fs_1.existsSync)(formDir)) {
578
+ (0, fs_1.mkdirSync)(formDir, { recursive: true });
579
+ }
580
+ filePath = (0, path_1.join)(formDir, `${toPascalCase(name)}Form.tsx`);
581
+ break;
582
+ }
583
+ default:
584
+ spinner.fail('Unknown generator type');
585
+ return;
586
+ }
587
+ // Check if file already exists
588
+ if ((0, fs_1.existsSync)(filePath)) {
589
+ spinner.warn('File already exists');
590
+ const overwrite = await prompt(' Overwrite? (y/N): ');
591
+ if (overwrite.toLowerCase() !== 'y') {
592
+ console.log(chalk_1.default.gray('\n Skipped.\n'));
593
+ return;
594
+ }
595
+ }
596
+ // Write the file
597
+ (0, fs_1.writeFileSync)(filePath, content);
598
+ const relativePath = filePath.replace(cwd, '').replace(/\\/g, '/');
599
+ spinner.succeed(`Generated ${relativePath}`);
600
+ console.log(chalk_1.default.green(`
601
+ ╔═══════════════════════════════════════════════════════════╗
602
+ ║ ${chalk_1.default.bold('✓ Code generated successfully!')} ║
603
+ ╚═══════════════════════════════════════════════════════════╝
604
+ `));
605
+ console.log(chalk_1.default.white(' Created:\n'));
606
+ console.log(chalk_1.default.cyan(` ${relativePath}\n`));
607
+ // Show next steps based on type
608
+ console.log(chalk_1.default.white(' Next steps:\n'));
609
+ switch (type) {
610
+ case 'component':
611
+ console.log(chalk_1.default.gray(' 1. Import and use the component in your page'));
612
+ console.log(chalk_1.default.gray(' 2. Add any additional props you need'));
613
+ console.log(chalk_1.default.gray(' 3. Style it with Tailwind classes\n'));
614
+ break;
615
+ case 'api':
616
+ console.log(chalk_1.default.gray(' 1. Implement the TODO sections'));
617
+ console.log(chalk_1.default.gray(' 2. Add authentication if needed'));
618
+ console.log(chalk_1.default.gray(' 3. Test with curl or your frontend\n'));
619
+ break;
620
+ case 'service':
621
+ console.log(chalk_1.default.gray(' 1. Update the Zod schemas'));
622
+ console.log(chalk_1.default.gray(' 2. Implement database queries'));
623
+ console.log(chalk_1.default.gray(' 3. Import and use in your API routes\n'));
624
+ break;
625
+ case 'hook':
626
+ console.log(chalk_1.default.gray(' 1. Implement the fetch logic'));
627
+ console.log(chalk_1.default.gray(' 2. Update the return type'));
628
+ console.log(chalk_1.default.gray(' 3. Use it in your components\n'));
629
+ break;
630
+ case 'page':
631
+ console.log(chalk_1.default.gray(` 1. Visit /${toKebabCase(name)} in your browser`));
632
+ console.log(chalk_1.default.gray(' 2. Add your page content'));
633
+ console.log(chalk_1.default.gray(' 3. Update the metadata\n'));
634
+ break;
635
+ case 'schema':
636
+ console.log(chalk_1.default.gray(' 1. Add your column definitions'));
637
+ console.log(chalk_1.default.gray(' 2. Export from src/db/schema.ts'));
638
+ console.log(chalk_1.default.gray(' 3. Run `npm run db:push` to sync\n'));
639
+ break;
640
+ case 'form':
641
+ console.log(chalk_1.default.gray(' 1. Update the Zod schema with your fields'));
642
+ console.log(chalk_1.default.gray(' 2. Add form inputs for each field'));
643
+ console.log(chalk_1.default.gray(' 3. Handle form submission in parent\n'));
644
+ break;
645
+ }
646
+ }
647
+ catch (error) {
648
+ spinner.fail('Generation failed');
649
+ const message = error instanceof Error ? error.message : 'Unknown error';
650
+ console.log(chalk_1.default.red(`\n Error: ${message}\n`));
651
+ process.exit(1);
652
+ }
653
+ }