codebakers 2.0.2 → 2.0.4
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/dist/index.js +582 -181
- package/package.json +1 -1
- package/src/commands/migrate.ts +419 -0
- package/src/index.ts +14 -0
- package/src/utils/config.ts +93 -1
package/package.json
CHANGED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { execa } from 'execa';
|
|
4
|
+
import * as fs from 'fs-extra';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { Config } from '../utils/config.js';
|
|
7
|
+
|
|
8
|
+
interface MigrateOptions {
|
|
9
|
+
push?: boolean;
|
|
10
|
+
generate?: boolean;
|
|
11
|
+
status?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function migrateCommand(options: MigrateOptions = {}): Promise<void> {
|
|
15
|
+
const config = new Config();
|
|
16
|
+
|
|
17
|
+
if (!config.isInProject()) {
|
|
18
|
+
p.log.error('Not in a CodeBakers project.');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
p.intro(chalk.bgCyan.black(' Database Migrations '));
|
|
23
|
+
|
|
24
|
+
// Detect migration tool
|
|
25
|
+
const migrationTool = await detectMigrationTool();
|
|
26
|
+
|
|
27
|
+
if (!migrationTool) {
|
|
28
|
+
p.log.error('No migration tool detected. Supported: Drizzle, Prisma, Supabase CLI');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
p.log.info(`Detected: ${migrationTool}`);
|
|
33
|
+
|
|
34
|
+
const action = options.push ? 'push' :
|
|
35
|
+
options.generate ? 'generate' :
|
|
36
|
+
options.status ? 'status' :
|
|
37
|
+
await p.select({
|
|
38
|
+
message: 'What do you want to do?',
|
|
39
|
+
options: [
|
|
40
|
+
{ value: 'status', label: '📊 Check migration status' },
|
|
41
|
+
{ value: 'generate', label: '📝 Generate migration' },
|
|
42
|
+
{ value: 'push', label: '🚀 Push to database' },
|
|
43
|
+
{ value: 'pull', label: '⬇️ Pull from database' },
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (p.isCancel(action)) return;
|
|
48
|
+
|
|
49
|
+
switch (action) {
|
|
50
|
+
case 'status':
|
|
51
|
+
await checkMigrationStatus(migrationTool);
|
|
52
|
+
break;
|
|
53
|
+
case 'generate':
|
|
54
|
+
await generateMigration(migrationTool);
|
|
55
|
+
break;
|
|
56
|
+
case 'push':
|
|
57
|
+
await pushMigration(migrationTool);
|
|
58
|
+
break;
|
|
59
|
+
case 'pull':
|
|
60
|
+
await pullSchema(migrationTool);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function detectMigrationTool(): Promise<string | null> {
|
|
66
|
+
const cwd = process.cwd();
|
|
67
|
+
|
|
68
|
+
// Check for Drizzle
|
|
69
|
+
if (fs.existsSync(path.join(cwd, 'drizzle.config.ts')) ||
|
|
70
|
+
fs.existsSync(path.join(cwd, 'drizzle.config.js'))) {
|
|
71
|
+
return 'drizzle';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check for Prisma
|
|
75
|
+
if (fs.existsSync(path.join(cwd, 'prisma', 'schema.prisma'))) {
|
|
76
|
+
return 'prisma';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check for Supabase
|
|
80
|
+
if (fs.existsSync(path.join(cwd, 'supabase', 'migrations'))) {
|
|
81
|
+
return 'supabase';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check package.json for clues
|
|
85
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
86
|
+
if (fs.existsSync(pkgPath)) {
|
|
87
|
+
const pkg = await fs.readJson(pkgPath);
|
|
88
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
89
|
+
|
|
90
|
+
if (deps['drizzle-orm'] || deps['drizzle-kit']) return 'drizzle';
|
|
91
|
+
if (deps['prisma'] || deps['@prisma/client']) return 'prisma';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function checkMigrationStatus(tool: string): Promise<void> {
|
|
98
|
+
const spinner = p.spinner();
|
|
99
|
+
spinner.start('Checking migration status...');
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
let result;
|
|
103
|
+
switch (tool) {
|
|
104
|
+
case 'drizzle':
|
|
105
|
+
result = await execa('npx', ['drizzle-kit', 'check'], { cwd: process.cwd(), reject: false });
|
|
106
|
+
break;
|
|
107
|
+
case 'prisma':
|
|
108
|
+
result = await execa('npx', ['prisma', 'migrate', 'status'], { cwd: process.cwd(), reject: false });
|
|
109
|
+
break;
|
|
110
|
+
case 'supabase':
|
|
111
|
+
result = await execa('npx', ['supabase', 'migration', 'list'], { cwd: process.cwd(), reject: false });
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
spinner.stop('Status check complete');
|
|
116
|
+
|
|
117
|
+
if (result?.stdout) {
|
|
118
|
+
console.log(chalk.dim(result.stdout));
|
|
119
|
+
}
|
|
120
|
+
if (result?.stderr) {
|
|
121
|
+
console.log(chalk.yellow(result.stderr));
|
|
122
|
+
}
|
|
123
|
+
} catch (error) {
|
|
124
|
+
spinner.stop('Error checking status');
|
|
125
|
+
p.log.error(error instanceof Error ? error.message : 'Unknown error');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function generateMigration(tool: string): Promise<void> {
|
|
130
|
+
const name = await p.text({
|
|
131
|
+
message: 'Migration name:',
|
|
132
|
+
placeholder: 'add_users_table',
|
|
133
|
+
validate: (v) => !v ? 'Name required' : undefined,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (p.isCancel(name)) return;
|
|
137
|
+
|
|
138
|
+
const spinner = p.spinner();
|
|
139
|
+
spinner.start('Generating migration...');
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
let result;
|
|
143
|
+
switch (tool) {
|
|
144
|
+
case 'drizzle':
|
|
145
|
+
result = await execa('npx', ['drizzle-kit', 'generate', '--name', name as string], { cwd: process.cwd(), reject: false });
|
|
146
|
+
break;
|
|
147
|
+
case 'prisma':
|
|
148
|
+
result = await execa('npx', ['prisma', 'migrate', 'dev', '--name', name as string, '--create-only'], { cwd: process.cwd(), reject: false });
|
|
149
|
+
break;
|
|
150
|
+
case 'supabase':
|
|
151
|
+
result = await execa('npx', ['supabase', 'migration', 'new', name as string], { cwd: process.cwd(), reject: false });
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
spinner.stop('Migration generated');
|
|
156
|
+
|
|
157
|
+
if (result?.stdout) {
|
|
158
|
+
console.log(chalk.dim(result.stdout));
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
spinner.stop('Error generating migration');
|
|
162
|
+
p.log.error(error instanceof Error ? error.message : 'Unknown error');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function pushMigration(tool: string): Promise<void> {
|
|
167
|
+
const spinner = p.spinner();
|
|
168
|
+
spinner.start('Pushing migration to database...');
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
let result;
|
|
172
|
+
let migrationSql = '';
|
|
173
|
+
|
|
174
|
+
switch (tool) {
|
|
175
|
+
case 'drizzle':
|
|
176
|
+
result = await execa('npx', ['drizzle-kit', 'push'], {
|
|
177
|
+
cwd: process.cwd(),
|
|
178
|
+
reject: false,
|
|
179
|
+
env: { ...process.env }
|
|
180
|
+
});
|
|
181
|
+
break;
|
|
182
|
+
case 'prisma':
|
|
183
|
+
result = await execa('npx', ['prisma', 'db', 'push'], {
|
|
184
|
+
cwd: process.cwd(),
|
|
185
|
+
reject: false
|
|
186
|
+
});
|
|
187
|
+
break;
|
|
188
|
+
case 'supabase':
|
|
189
|
+
result = await execa('npx', ['supabase', 'db', 'push'], {
|
|
190
|
+
cwd: process.cwd(),
|
|
191
|
+
reject: false
|
|
192
|
+
});
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check if push failed
|
|
197
|
+
if (result?.exitCode !== 0) {
|
|
198
|
+
spinner.stop('Migration push failed');
|
|
199
|
+
|
|
200
|
+
const errorOutput = result?.stderr || result?.stdout || '';
|
|
201
|
+
console.log(chalk.red('\nError:\n'));
|
|
202
|
+
console.log(chalk.dim(errorOutput));
|
|
203
|
+
|
|
204
|
+
// Try to get the SQL that needs to be run
|
|
205
|
+
migrationSql = await extractMigrationSQL(tool, errorOutput);
|
|
206
|
+
|
|
207
|
+
if (migrationSql) {
|
|
208
|
+
// Copy to clipboard
|
|
209
|
+
const copied = await copyToClipboard(migrationSql);
|
|
210
|
+
|
|
211
|
+
if (copied) {
|
|
212
|
+
console.log(chalk.green(`
|
|
213
|
+
╔════════════════════════════════════════════════════════════════╗
|
|
214
|
+
║ 📋 MIGRATION SQL COPIED TO CLIPBOARD! ║
|
|
215
|
+
╠════════════════════════════════════════════════════════════════╣
|
|
216
|
+
║ ║
|
|
217
|
+
║ The migration could not be pushed automatically. ║
|
|
218
|
+
║ The SQL has been copied to your clipboard. ║
|
|
219
|
+
║ ║
|
|
220
|
+
║ Next steps: ║
|
|
221
|
+
║ 1. Go to your Supabase Dashboard → SQL Editor ║
|
|
222
|
+
║ 2. Paste the SQL (Ctrl+V / Cmd+V) ║
|
|
223
|
+
║ 3. Review and run it ║
|
|
224
|
+
║ ║
|
|
225
|
+
╚════════════════════════════════════════════════════════════════╝
|
|
226
|
+
`));
|
|
227
|
+
|
|
228
|
+
// Also show the SQL
|
|
229
|
+
const showSql = await p.confirm({
|
|
230
|
+
message: 'Show the SQL here too?',
|
|
231
|
+
initialValue: true,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (showSql && !p.isCancel(showSql)) {
|
|
235
|
+
console.log(chalk.cyan('\n--- SQL Migration ---\n'));
|
|
236
|
+
console.log(migrationSql);
|
|
237
|
+
console.log(chalk.cyan('\n--- End SQL ---\n'));
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
// Clipboard failed, just show the SQL
|
|
241
|
+
console.log(chalk.yellow(`
|
|
242
|
+
╔════════════════════════════════════════════════════════════════╗
|
|
243
|
+
║ ⚠️ Could not copy to clipboard ║
|
|
244
|
+
║ ║
|
|
245
|
+
║ Here's the SQL to run manually: ║
|
|
246
|
+
╚════════════════════════════════════════════════════════════════╝
|
|
247
|
+
`));
|
|
248
|
+
console.log(chalk.cyan(migrationSql));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Save to file as backup
|
|
252
|
+
const sqlPath = path.join(process.cwd(), '.codebakers', 'failed-migration.sql');
|
|
253
|
+
await fs.ensureDir(path.dirname(sqlPath));
|
|
254
|
+
await fs.writeFile(sqlPath, migrationSql);
|
|
255
|
+
console.log(chalk.dim(`\nAlso saved to: ${sqlPath}\n`));
|
|
256
|
+
|
|
257
|
+
} else {
|
|
258
|
+
p.log.error('Could not extract migration SQL. Check the error above.');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
spinner.stop('Migration pushed successfully!');
|
|
265
|
+
|
|
266
|
+
if (result?.stdout) {
|
|
267
|
+
console.log(chalk.dim(result.stdout));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
p.log.success('Database updated!');
|
|
271
|
+
|
|
272
|
+
} catch (error) {
|
|
273
|
+
spinner.stop('Error');
|
|
274
|
+
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
275
|
+
p.log.error(errorMsg);
|
|
276
|
+
|
|
277
|
+
// Try to extract and copy SQL even from exception
|
|
278
|
+
const migrationSql = await extractMigrationSQL(tool, errorMsg);
|
|
279
|
+
if (migrationSql) {
|
|
280
|
+
await copyToClipboard(migrationSql);
|
|
281
|
+
console.log(chalk.green('\n📋 Migration SQL copied to clipboard!\n'));
|
|
282
|
+
console.log(chalk.cyan(migrationSql));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function pullSchema(tool: string): Promise<void> {
|
|
288
|
+
const spinner = p.spinner();
|
|
289
|
+
spinner.start('Pulling schema from database...');
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
let result;
|
|
293
|
+
switch (tool) {
|
|
294
|
+
case 'drizzle':
|
|
295
|
+
result = await execa('npx', ['drizzle-kit', 'introspect'], { cwd: process.cwd(), reject: false });
|
|
296
|
+
break;
|
|
297
|
+
case 'prisma':
|
|
298
|
+
result = await execa('npx', ['prisma', 'db', 'pull'], { cwd: process.cwd(), reject: false });
|
|
299
|
+
break;
|
|
300
|
+
case 'supabase':
|
|
301
|
+
result = await execa('npx', ['supabase', 'db', 'pull'], { cwd: process.cwd(), reject: false });
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
spinner.stop('Schema pulled');
|
|
306
|
+
|
|
307
|
+
if (result?.stdout) {
|
|
308
|
+
console.log(chalk.dim(result.stdout));
|
|
309
|
+
}
|
|
310
|
+
} catch (error) {
|
|
311
|
+
spinner.stop('Error pulling schema');
|
|
312
|
+
p.log.error(error instanceof Error ? error.message : 'Unknown error');
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function extractMigrationSQL(tool: string, errorOutput: string): Promise<string | null> {
|
|
317
|
+
const cwd = process.cwd();
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
switch (tool) {
|
|
321
|
+
case 'drizzle': {
|
|
322
|
+
// Check for pending migrations in drizzle folder
|
|
323
|
+
const drizzleDir = path.join(cwd, 'drizzle');
|
|
324
|
+
if (fs.existsSync(drizzleDir)) {
|
|
325
|
+
const files = await fs.readdir(drizzleDir);
|
|
326
|
+
const sqlFiles = files.filter(f => f.endsWith('.sql')).sort().reverse();
|
|
327
|
+
if (sqlFiles.length > 0) {
|
|
328
|
+
const latestMigration = await fs.readFile(path.join(drizzleDir, sqlFiles[0]), 'utf-8');
|
|
329
|
+
return latestMigration;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Try to generate SQL
|
|
334
|
+
const result = await execa('npx', ['drizzle-kit', 'generate', '--sql'], {
|
|
335
|
+
cwd,
|
|
336
|
+
reject: false
|
|
337
|
+
});
|
|
338
|
+
if (result?.stdout) {
|
|
339
|
+
// Extract SQL from output
|
|
340
|
+
const sqlMatch = result.stdout.match(/```sql([\s\S]*?)```/);
|
|
341
|
+
if (sqlMatch) return sqlMatch[1].trim();
|
|
342
|
+
return result.stdout;
|
|
343
|
+
}
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
case 'prisma': {
|
|
348
|
+
// Check for pending migrations
|
|
349
|
+
const migrationsDir = path.join(cwd, 'prisma', 'migrations');
|
|
350
|
+
if (fs.existsSync(migrationsDir)) {
|
|
351
|
+
const migrations = await fs.readdir(migrationsDir);
|
|
352
|
+
const sorted = migrations.filter(m => m !== 'migration_lock.toml').sort().reverse();
|
|
353
|
+
if (sorted.length > 0) {
|
|
354
|
+
const migrationPath = path.join(migrationsDir, sorted[0], 'migration.sql');
|
|
355
|
+
if (fs.existsSync(migrationPath)) {
|
|
356
|
+
return await fs.readFile(migrationPath, 'utf-8');
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
case 'supabase': {
|
|
364
|
+
// Check supabase migrations folder
|
|
365
|
+
const migrationsDir = path.join(cwd, 'supabase', 'migrations');
|
|
366
|
+
if (fs.existsSync(migrationsDir)) {
|
|
367
|
+
const files = await fs.readdir(migrationsDir);
|
|
368
|
+
const sqlFiles = files.filter(f => f.endsWith('.sql')).sort().reverse();
|
|
369
|
+
if (sqlFiles.length > 0) {
|
|
370
|
+
return await fs.readFile(path.join(migrationsDir, sqlFiles[0]), 'utf-8');
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Try to extract SQL from error message itself
|
|
378
|
+
const sqlMatch = errorOutput.match(/(CREATE|ALTER|DROP|INSERT|UPDATE|DELETE)[\s\S]+?;/gi);
|
|
379
|
+
if (sqlMatch) {
|
|
380
|
+
return sqlMatch.join('\n\n');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
} catch {
|
|
384
|
+
// Silent fail
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async function copyToClipboard(text: string): Promise<boolean> {
|
|
391
|
+
try {
|
|
392
|
+
// Try different clipboard methods based on OS
|
|
393
|
+
const platform = process.platform;
|
|
394
|
+
|
|
395
|
+
if (platform === 'win32') {
|
|
396
|
+
// Windows: use clip command
|
|
397
|
+
const proc = await execa('clip', { input: text, reject: false });
|
|
398
|
+
return proc.exitCode === 0;
|
|
399
|
+
} else if (platform === 'darwin') {
|
|
400
|
+
// macOS: use pbcopy
|
|
401
|
+
const proc = await execa('pbcopy', { input: text, reject: false });
|
|
402
|
+
return proc.exitCode === 0;
|
|
403
|
+
} else {
|
|
404
|
+
// Linux: try xclip or xsel
|
|
405
|
+
try {
|
|
406
|
+
const proc = await execa('xclip', ['-selection', 'clipboard'], { input: text, reject: false });
|
|
407
|
+
return proc.exitCode === 0;
|
|
408
|
+
} catch {
|
|
409
|
+
const proc = await execa('xsel', ['--clipboard', '--input'], { input: text, reject: false });
|
|
410
|
+
return proc.exitCode === 0;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
} catch {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Export for use in other commands
|
|
419
|
+
export { copyToClipboard, extractMigrationSQL };
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { fixCommand } from './commands/fix.js';
|
|
|
22
22
|
import { designCommand } from './commands/design.js';
|
|
23
23
|
import { prdCommand } from './commands/prd.js';
|
|
24
24
|
import { advisorsCommand } from './commands/advisors.js';
|
|
25
|
+
import { migrateCommand } from './commands/migrate.js';
|
|
25
26
|
|
|
26
27
|
const VERSION = '1.0.0';
|
|
27
28
|
|
|
@@ -64,6 +65,7 @@ async function showMainMenu(): Promise<void> {
|
|
|
64
65
|
{ value: 'code', label: '💬 Code with AI', hint: 'build features, fix bugs' },
|
|
65
66
|
{ value: 'check', label: '🔍 Check code quality', hint: 'run pattern enforcement' },
|
|
66
67
|
{ value: 'deploy', label: '🚀 Deploy', hint: 'deploy to Vercel' },
|
|
68
|
+
{ value: 'migrate', label: '🗄️ Database migrations', hint: 'push, generate, status' },
|
|
67
69
|
{ value: 'fix', label: '🔧 Fix errors', hint: 'auto-fix build/deploy errors' },
|
|
68
70
|
{ value: 'generate', label: '⚡ Generate', hint: 'scaffold components, pages' },
|
|
69
71
|
{ value: 'status', label: '📊 Project status', hint: 'view project health' },
|
|
@@ -110,6 +112,9 @@ async function showMainMenu(): Promise<void> {
|
|
|
110
112
|
case 'deploy':
|
|
111
113
|
await deployCommand();
|
|
112
114
|
break;
|
|
115
|
+
case 'migrate':
|
|
116
|
+
await migrateCommand();
|
|
117
|
+
break;
|
|
113
118
|
case 'fix':
|
|
114
119
|
await fixCommand();
|
|
115
120
|
break;
|
|
@@ -287,6 +292,15 @@ program
|
|
|
287
292
|
.description('Consult with the CodeBakers Dream Team advisory board')
|
|
288
293
|
.action(advisorsCommand);
|
|
289
294
|
|
|
295
|
+
program
|
|
296
|
+
.command('migrate')
|
|
297
|
+
.alias('db')
|
|
298
|
+
.description('Database migrations (push, generate, status)')
|
|
299
|
+
.option('--push', 'Push migrations to database')
|
|
300
|
+
.option('--generate', 'Generate new migration')
|
|
301
|
+
.option('--status', 'Check migration status')
|
|
302
|
+
.action(migrateCommand);
|
|
303
|
+
|
|
290
304
|
// Parse args or show menu
|
|
291
305
|
const args = process.argv.slice(2);
|
|
292
306
|
|
package/src/utils/config.ts
CHANGED
|
@@ -141,7 +141,99 @@ export class Config {
|
|
|
141
141
|
const cwd = process.cwd();
|
|
142
142
|
const codebakersDir = path.join(cwd, '.codebakers');
|
|
143
143
|
const claudeFile = path.join(cwd, 'CLAUDE.md');
|
|
144
|
-
|
|
144
|
+
|
|
145
|
+
// Already a CodeBakers project
|
|
146
|
+
if (fs.existsSync(codebakersDir) || fs.existsSync(claudeFile)) {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Auto-detect existing projects by common files
|
|
151
|
+
const projectIndicators = [
|
|
152
|
+
'package.json',
|
|
153
|
+
'next.config.js',
|
|
154
|
+
'next.config.mjs',
|
|
155
|
+
'next.config.ts',
|
|
156
|
+
'vite.config.ts',
|
|
157
|
+
'vite.config.js',
|
|
158
|
+
'remix.config.js',
|
|
159
|
+
'astro.config.mjs',
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
const hasProjectFile = projectIndicators.some(file =>
|
|
163
|
+
fs.existsSync(path.join(cwd, file))
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// If it's a project but no .codebakers, auto-initialize
|
|
167
|
+
if (hasProjectFile) {
|
|
168
|
+
this.autoInitExisting(cwd);
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Auto-initialize CodeBakers in an existing project
|
|
176
|
+
private autoInitExisting(cwd: string): void {
|
|
177
|
+
try {
|
|
178
|
+
// Detect framework
|
|
179
|
+
let framework = 'unknown';
|
|
180
|
+
let ui = 'unknown';
|
|
181
|
+
|
|
182
|
+
if (fs.existsSync(path.join(cwd, 'next.config.js')) ||
|
|
183
|
+
fs.existsSync(path.join(cwd, 'next.config.mjs')) ||
|
|
184
|
+
fs.existsSync(path.join(cwd, 'next.config.ts'))) {
|
|
185
|
+
framework = 'nextjs';
|
|
186
|
+
} else if (fs.existsSync(path.join(cwd, 'vite.config.ts')) ||
|
|
187
|
+
fs.existsSync(path.join(cwd, 'vite.config.js'))) {
|
|
188
|
+
framework = 'vite';
|
|
189
|
+
} else if (fs.existsSync(path.join(cwd, 'remix.config.js'))) {
|
|
190
|
+
framework = 'remix';
|
|
191
|
+
} else if (fs.existsSync(path.join(cwd, 'astro.config.mjs'))) {
|
|
192
|
+
framework = 'astro';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Check for UI libraries in package.json
|
|
196
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
197
|
+
if (fs.existsSync(pkgPath)) {
|
|
198
|
+
const pkg = fs.readJsonSync(pkgPath);
|
|
199
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
200
|
+
|
|
201
|
+
if (deps['@shadcn/ui'] || deps['class-variance-authority']) {
|
|
202
|
+
ui = 'shadcn';
|
|
203
|
+
} else if (deps['@chakra-ui/react']) {
|
|
204
|
+
ui = 'chakra';
|
|
205
|
+
} else if (deps['@mantine/core']) {
|
|
206
|
+
ui = 'mantine';
|
|
207
|
+
} else if (deps['@mui/material']) {
|
|
208
|
+
ui = 'mui';
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Create .codebakers config
|
|
213
|
+
const configDir = path.join(cwd, '.codebakers');
|
|
214
|
+
fs.ensureDirSync(configDir);
|
|
215
|
+
|
|
216
|
+
const config = {
|
|
217
|
+
name: path.basename(cwd),
|
|
218
|
+
framework,
|
|
219
|
+
ui,
|
|
220
|
+
createdAt: new Date().toISOString(),
|
|
221
|
+
autoDetected: true,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
fs.writeJsonSync(path.join(configDir, 'config.json'), config, { spaces: 2 });
|
|
225
|
+
|
|
226
|
+
// Add to .gitignore if exists
|
|
227
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
228
|
+
if (fs.existsSync(gitignorePath)) {
|
|
229
|
+
const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
|
|
230
|
+
if (!gitignore.includes('.codebakers')) {
|
|
231
|
+
fs.appendFileSync(gitignorePath, '\n# CodeBakers\n.codebakers/\n');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
} catch {
|
|
235
|
+
// Silent fail - don't block if auto-init fails
|
|
236
|
+
}
|
|
145
237
|
}
|
|
146
238
|
|
|
147
239
|
// Get current project config
|