@codebakers/cli 2.8.0 → 2.9.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/dist/index.js +1 -1
- package/dist/mcp/server.js +294 -10
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/mcp/server.ts +312 -12
package/dist/index.js
CHANGED
|
@@ -64,7 +64,7 @@ const program = new commander_1.Command();
|
|
|
64
64
|
program
|
|
65
65
|
.name('codebakers')
|
|
66
66
|
.description('CodeBakers CLI - Production patterns for AI-assisted development')
|
|
67
|
-
.version('2.
|
|
67
|
+
.version('2.9.0');
|
|
68
68
|
// Primary command - one-time setup
|
|
69
69
|
program
|
|
70
70
|
.command('setup')
|
package/dist/mcp/server.js
CHANGED
|
@@ -509,7 +509,7 @@ class CodeBakersServer {
|
|
|
509
509
|
},
|
|
510
510
|
{
|
|
511
511
|
name: 'scaffold_project',
|
|
512
|
-
description: 'Create a new project from scratch with Next.js + Supabase + Drizzle. Use this when user wants to build something new and no project exists yet. Creates all files, installs dependencies, and sets up CodeBakers patterns automatically.',
|
|
512
|
+
description: 'Create a new project from scratch with Next.js + Supabase + Drizzle. Use this when user wants to build something new and no project exists yet. Creates all files, installs dependencies, and sets up CodeBakers patterns automatically. Set fullDeploy=true for seamless idea-to-deployment (creates GitHub repo, Supabase project, and deploys to Vercel). When fullDeploy=true, first call returns explanation - then call again with deployConfirmed=true after user confirms.',
|
|
513
513
|
inputSchema: {
|
|
514
514
|
type: 'object',
|
|
515
515
|
properties: {
|
|
@@ -521,6 +521,14 @@ class CodeBakersServer {
|
|
|
521
521
|
type: 'string',
|
|
522
522
|
description: 'Brief description of what the project is for (used in PRD.md)',
|
|
523
523
|
},
|
|
524
|
+
fullDeploy: {
|
|
525
|
+
type: 'boolean',
|
|
526
|
+
description: 'If true, enables full deployment flow (GitHub + Supabase + Vercel). First call returns explanation for user confirmation.',
|
|
527
|
+
},
|
|
528
|
+
deployConfirmed: {
|
|
529
|
+
type: 'boolean',
|
|
530
|
+
description: 'Set to true AFTER user confirms they want full deployment. Only set this after showing user the explanation and getting their approval.',
|
|
531
|
+
},
|
|
524
532
|
},
|
|
525
533
|
required: ['projectName'],
|
|
526
534
|
},
|
|
@@ -1204,8 +1212,12 @@ Show the user what their simple request was expanded into, then proceed with the
|
|
|
1204
1212
|
};
|
|
1205
1213
|
}
|
|
1206
1214
|
async handleScaffoldProject(args) {
|
|
1207
|
-
const { projectName, description } = args;
|
|
1215
|
+
const { projectName, description, fullDeploy, deployConfirmed } = args;
|
|
1208
1216
|
const cwd = process.cwd();
|
|
1217
|
+
// If fullDeploy requested but not confirmed, show explanation and ask for confirmation
|
|
1218
|
+
if (fullDeploy && !deployConfirmed) {
|
|
1219
|
+
return this.showFullDeployExplanation(projectName, description);
|
|
1220
|
+
}
|
|
1209
1221
|
// Check if directory has files
|
|
1210
1222
|
const files = fs.readdirSync(cwd);
|
|
1211
1223
|
const hasFiles = files.filter(f => !f.startsWith('.')).length > 0;
|
|
@@ -1344,14 +1356,22 @@ phase: setup
|
|
|
1344
1356
|
}
|
|
1345
1357
|
results.push('\n---\n');
|
|
1346
1358
|
results.push('## ✅ Project Created Successfully!\n');
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1359
|
+
// If fullDeploy is enabled and confirmed, proceed with cloud deployment
|
|
1360
|
+
if (fullDeploy && deployConfirmed) {
|
|
1361
|
+
results.push('## 🚀 Starting Full Deployment...\n');
|
|
1362
|
+
const deployResults = await this.executeFullDeploy(projectName, cwd, description);
|
|
1363
|
+
results.push(...deployResults);
|
|
1364
|
+
}
|
|
1365
|
+
else {
|
|
1366
|
+
results.push('### Next Steps:\n');
|
|
1367
|
+
results.push('1. **Set up Supabase:** Go to https://supabase.com and create a free project');
|
|
1368
|
+
results.push('2. **Add credentials:** Copy your Supabase URL and anon key to `.env.local`');
|
|
1369
|
+
results.push('3. **Start building:** Just tell me what features you want!\n');
|
|
1370
|
+
results.push('### Example:\n');
|
|
1371
|
+
results.push('> "Add user authentication with email/password"');
|
|
1372
|
+
results.push('> "Create a dashboard with stats cards"');
|
|
1373
|
+
results.push('> "Build a todo list with CRUD operations"');
|
|
1374
|
+
}
|
|
1355
1375
|
}
|
|
1356
1376
|
catch (error) {
|
|
1357
1377
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
@@ -1364,6 +1384,270 @@ phase: setup
|
|
|
1364
1384
|
}],
|
|
1365
1385
|
};
|
|
1366
1386
|
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Show explanation of what fullDeploy will do and ask for confirmation
|
|
1389
|
+
*/
|
|
1390
|
+
showFullDeployExplanation(projectName, description) {
|
|
1391
|
+
const explanation = `# 🚀 Full Deployment: ${projectName}
|
|
1392
|
+
|
|
1393
|
+
## What This Will Do
|
|
1394
|
+
|
|
1395
|
+
Full deployment creates a complete production-ready environment automatically:
|
|
1396
|
+
|
|
1397
|
+
### 1. 📁 Local Project
|
|
1398
|
+
- Create Next.js + Supabase + Drizzle project
|
|
1399
|
+
- Install all dependencies
|
|
1400
|
+
- Set up CodeBakers patterns
|
|
1401
|
+
|
|
1402
|
+
### 2. 🐙 GitHub Repository
|
|
1403
|
+
- Create a new private repository: \`${projectName}\`
|
|
1404
|
+
- Initialize git and push code
|
|
1405
|
+
- Set up .gitignore properly
|
|
1406
|
+
|
|
1407
|
+
### 3. 🗄️ Supabase Project
|
|
1408
|
+
- Create a new Supabase project
|
|
1409
|
+
- Get database connection string
|
|
1410
|
+
- Get API keys (anon + service role)
|
|
1411
|
+
- Auto-configure .env.local
|
|
1412
|
+
|
|
1413
|
+
### 4. 🔺 Vercel Deployment
|
|
1414
|
+
- Deploy to Vercel
|
|
1415
|
+
- Connect to GitHub for auto-deploys
|
|
1416
|
+
- Set all environment variables
|
|
1417
|
+
- Get your live URL
|
|
1418
|
+
|
|
1419
|
+
---
|
|
1420
|
+
|
|
1421
|
+
## Requirements
|
|
1422
|
+
|
|
1423
|
+
Make sure you have these CLIs installed and authenticated:
|
|
1424
|
+
- \`gh\` - GitHub CLI (run: \`gh auth login\`)
|
|
1425
|
+
- \`supabase\` - Supabase CLI (run: \`supabase login\`)
|
|
1426
|
+
- \`vercel\` - Vercel CLI (run: \`vercel login\`)
|
|
1427
|
+
|
|
1428
|
+
---
|
|
1429
|
+
|
|
1430
|
+
## 🎯 Result
|
|
1431
|
+
|
|
1432
|
+
After completion, you'll have:
|
|
1433
|
+
- ✅ GitHub repo with your code
|
|
1434
|
+
- ✅ Supabase project with database ready
|
|
1435
|
+
- ✅ Live URL on Vercel
|
|
1436
|
+
- ✅ Auto-deploys on every push
|
|
1437
|
+
|
|
1438
|
+
---
|
|
1439
|
+
|
|
1440
|
+
**⚠️ IMPORTANT: Ask the user to confirm before proceeding.**
|
|
1441
|
+
|
|
1442
|
+
To proceed, call \`scaffold_project\` again with:
|
|
1443
|
+
\`\`\`json
|
|
1444
|
+
{
|
|
1445
|
+
"projectName": "${projectName}",
|
|
1446
|
+
"description": "${description || ''}",
|
|
1447
|
+
"fullDeploy": true,
|
|
1448
|
+
"deployConfirmed": true
|
|
1449
|
+
}
|
|
1450
|
+
\`\`\`
|
|
1451
|
+
|
|
1452
|
+
Or if user declines, call without fullDeploy:
|
|
1453
|
+
\`\`\`json
|
|
1454
|
+
{
|
|
1455
|
+
"projectName": "${projectName}",
|
|
1456
|
+
"description": "${description || ''}"
|
|
1457
|
+
}
|
|
1458
|
+
\`\`\`
|
|
1459
|
+
`;
|
|
1460
|
+
return {
|
|
1461
|
+
content: [{
|
|
1462
|
+
type: 'text',
|
|
1463
|
+
text: explanation,
|
|
1464
|
+
}],
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Execute full cloud deployment (GitHub + Supabase + Vercel)
|
|
1469
|
+
*/
|
|
1470
|
+
async executeFullDeploy(projectName, cwd, description) {
|
|
1471
|
+
const results = [];
|
|
1472
|
+
// Check for required CLIs
|
|
1473
|
+
const cliChecks = this.checkRequiredCLIs();
|
|
1474
|
+
if (cliChecks.missing.length > 0) {
|
|
1475
|
+
results.push('### ❌ Missing Required CLIs\n');
|
|
1476
|
+
results.push('The following CLIs are required for full deployment:\n');
|
|
1477
|
+
for (const cli of cliChecks.missing) {
|
|
1478
|
+
results.push(`- **${cli.name}**: ${cli.installCmd}`);
|
|
1479
|
+
}
|
|
1480
|
+
results.push('\nInstall the missing CLIs and try again.');
|
|
1481
|
+
return results;
|
|
1482
|
+
}
|
|
1483
|
+
results.push('✓ All required CLIs found\n');
|
|
1484
|
+
// Step 1: Initialize Git and create GitHub repo
|
|
1485
|
+
results.push('### Step 1: GitHub Repository\n');
|
|
1486
|
+
try {
|
|
1487
|
+
// Initialize git
|
|
1488
|
+
(0, child_process_1.execSync)('git init', { cwd, stdio: 'pipe' });
|
|
1489
|
+
(0, child_process_1.execSync)('git add .', { cwd, stdio: 'pipe' });
|
|
1490
|
+
(0, child_process_1.execSync)('git commit -m "Initial commit from CodeBakers"', { cwd, stdio: 'pipe' });
|
|
1491
|
+
results.push('✓ Initialized git repository');
|
|
1492
|
+
// Create GitHub repo
|
|
1493
|
+
const ghDescription = description || `${projectName} - Created with CodeBakers`;
|
|
1494
|
+
(0, child_process_1.execSync)(`gh repo create ${projectName} --private --source=. --push --description "${ghDescription}"`, { cwd, stdio: 'pipe' });
|
|
1495
|
+
results.push(`✓ Created GitHub repo: ${projectName}`);
|
|
1496
|
+
results.push(` → https://github.com/${this.getGitHubUsername()}/${projectName}\n`);
|
|
1497
|
+
}
|
|
1498
|
+
catch (error) {
|
|
1499
|
+
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
1500
|
+
results.push(`⚠️ GitHub setup failed: ${msg}`);
|
|
1501
|
+
results.push(' You can create the repo manually: gh repo create\n');
|
|
1502
|
+
}
|
|
1503
|
+
// Step 2: Create Supabase project
|
|
1504
|
+
results.push('### Step 2: Supabase Project\n');
|
|
1505
|
+
try {
|
|
1506
|
+
// Create Supabase project (this may take a while)
|
|
1507
|
+
const orgId = this.getSupabaseOrgId();
|
|
1508
|
+
if (orgId) {
|
|
1509
|
+
(0, child_process_1.execSync)(`supabase projects create ${projectName} --org-id ${orgId} --region us-east-1 --db-password "${this.generatePassword()}"`, { cwd, stdio: 'pipe', timeout: 120000 });
|
|
1510
|
+
results.push(`✓ Created Supabase project: ${projectName}`);
|
|
1511
|
+
// Get project credentials
|
|
1512
|
+
const projectsOutput = (0, child_process_1.execSync)('supabase projects list --output json', { cwd, encoding: 'utf-8' });
|
|
1513
|
+
const projects = JSON.parse(projectsOutput);
|
|
1514
|
+
const newProject = projects.find((p) => p.name === projectName);
|
|
1515
|
+
if (newProject) {
|
|
1516
|
+
// Update .env.local with Supabase credentials
|
|
1517
|
+
const envPath = path.join(cwd, '.env.local');
|
|
1518
|
+
let envContent = fs.readFileSync(envPath, 'utf-8');
|
|
1519
|
+
envContent = envContent.replace('your-supabase-url', `https://${newProject.id}.supabase.co`);
|
|
1520
|
+
envContent = envContent.replace('your-anon-key', newProject.anon_key || 'YOUR_ANON_KEY');
|
|
1521
|
+
fs.writeFileSync(envPath, envContent);
|
|
1522
|
+
results.push('✓ Updated .env.local with Supabase credentials\n');
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
else {
|
|
1526
|
+
results.push('⚠️ Could not detect Supabase organization');
|
|
1527
|
+
results.push(' Run: supabase orgs list\n');
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
catch (error) {
|
|
1531
|
+
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
1532
|
+
results.push(`⚠️ Supabase setup failed: ${msg}`);
|
|
1533
|
+
results.push(' Create project manually at: https://supabase.com/dashboard\n');
|
|
1534
|
+
}
|
|
1535
|
+
// Step 3: Deploy to Vercel
|
|
1536
|
+
results.push('### Step 3: Vercel Deployment\n');
|
|
1537
|
+
try {
|
|
1538
|
+
// Link to Vercel (creates new project)
|
|
1539
|
+
(0, child_process_1.execSync)('vercel link --yes', { cwd, stdio: 'pipe' });
|
|
1540
|
+
results.push('✓ Linked to Vercel');
|
|
1541
|
+
// Set environment variables from .env.local
|
|
1542
|
+
const envPath = path.join(cwd, '.env.local');
|
|
1543
|
+
if (fs.existsSync(envPath)) {
|
|
1544
|
+
const envContent = fs.readFileSync(envPath, 'utf-8');
|
|
1545
|
+
const envVars = envContent.split('\n')
|
|
1546
|
+
.filter(line => line.includes('=') && !line.startsWith('#'))
|
|
1547
|
+
.map(line => {
|
|
1548
|
+
const [key, ...valueParts] = line.split('=');
|
|
1549
|
+
return { key: key.trim(), value: valueParts.join('=').trim() };
|
|
1550
|
+
});
|
|
1551
|
+
for (const { key, value } of envVars) {
|
|
1552
|
+
if (value && !value.includes('your-')) {
|
|
1553
|
+
try {
|
|
1554
|
+
(0, child_process_1.execSync)(`vercel env add ${key} production <<< "${value}"`, { cwd, stdio: 'pipe', shell: 'bash' });
|
|
1555
|
+
}
|
|
1556
|
+
catch {
|
|
1557
|
+
// Env var might already exist, try to update
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
results.push('✓ Set environment variables');
|
|
1562
|
+
}
|
|
1563
|
+
// Deploy to production
|
|
1564
|
+
const deployOutput = (0, child_process_1.execSync)('vercel --prod --yes', { cwd, encoding: 'utf-8' });
|
|
1565
|
+
const urlMatch = deployOutput.match(/https:\/\/[^\s]+\.vercel\.app/);
|
|
1566
|
+
const deployUrl = urlMatch ? urlMatch[0] : 'Check Vercel dashboard';
|
|
1567
|
+
results.push(`✓ Deployed to Vercel`);
|
|
1568
|
+
results.push(` → ${deployUrl}\n`);
|
|
1569
|
+
// Connect to GitHub for auto-deploys
|
|
1570
|
+
try {
|
|
1571
|
+
(0, child_process_1.execSync)('vercel git connect --yes', { cwd, stdio: 'pipe' });
|
|
1572
|
+
results.push('✓ Connected to GitHub for auto-deploys\n');
|
|
1573
|
+
}
|
|
1574
|
+
catch {
|
|
1575
|
+
results.push('⚠️ Could not auto-connect to GitHub\n');
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
catch (error) {
|
|
1579
|
+
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
1580
|
+
results.push(`⚠️ Vercel deployment failed: ${msg}`);
|
|
1581
|
+
results.push(' Deploy manually: vercel --prod\n');
|
|
1582
|
+
}
|
|
1583
|
+
// Summary
|
|
1584
|
+
results.push('---\n');
|
|
1585
|
+
results.push('## 🎉 Full Deployment Complete!\n');
|
|
1586
|
+
results.push('Your project is now live with:');
|
|
1587
|
+
results.push('- GitHub repo with CI/CD ready');
|
|
1588
|
+
results.push('- Supabase database configured');
|
|
1589
|
+
results.push('- Vercel hosting with auto-deploys\n');
|
|
1590
|
+
results.push('**Start building features - every push auto-deploys!**');
|
|
1591
|
+
return results;
|
|
1592
|
+
}
|
|
1593
|
+
/**
|
|
1594
|
+
* Check if required CLIs are installed
|
|
1595
|
+
*/
|
|
1596
|
+
checkRequiredCLIs() {
|
|
1597
|
+
const clis = [
|
|
1598
|
+
{ name: 'gh', cmd: 'gh --version', installCmd: 'npm install -g gh' },
|
|
1599
|
+
{ name: 'supabase', cmd: 'supabase --version', installCmd: 'npm install -g supabase' },
|
|
1600
|
+
{ name: 'vercel', cmd: 'vercel --version', installCmd: 'npm install -g vercel' },
|
|
1601
|
+
];
|
|
1602
|
+
const installed = [];
|
|
1603
|
+
const missing = [];
|
|
1604
|
+
for (const cli of clis) {
|
|
1605
|
+
try {
|
|
1606
|
+
(0, child_process_1.execSync)(cli.cmd, { stdio: 'pipe' });
|
|
1607
|
+
installed.push(cli.name);
|
|
1608
|
+
}
|
|
1609
|
+
catch {
|
|
1610
|
+
missing.push({ name: cli.name, installCmd: cli.installCmd });
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
return { installed, missing };
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Get GitHub username from gh CLI
|
|
1617
|
+
*/
|
|
1618
|
+
getGitHubUsername() {
|
|
1619
|
+
try {
|
|
1620
|
+
const output = (0, child_process_1.execSync)('gh api user --jq .login', { encoding: 'utf-8' });
|
|
1621
|
+
return output.trim();
|
|
1622
|
+
}
|
|
1623
|
+
catch {
|
|
1624
|
+
return 'YOUR_USERNAME';
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Get Supabase organization ID
|
|
1629
|
+
*/
|
|
1630
|
+
getSupabaseOrgId() {
|
|
1631
|
+
try {
|
|
1632
|
+
const output = (0, child_process_1.execSync)('supabase orgs list --output json', { encoding: 'utf-8' });
|
|
1633
|
+
const orgs = JSON.parse(output);
|
|
1634
|
+
return orgs[0]?.id || null;
|
|
1635
|
+
}
|
|
1636
|
+
catch {
|
|
1637
|
+
return null;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Generate a secure random password for Supabase
|
|
1642
|
+
*/
|
|
1643
|
+
generatePassword() {
|
|
1644
|
+
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
1645
|
+
let password = '';
|
|
1646
|
+
for (let i = 0; i < 24; i++) {
|
|
1647
|
+
password += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
1648
|
+
}
|
|
1649
|
+
return password;
|
|
1650
|
+
}
|
|
1367
1651
|
async handleInitProject(args) {
|
|
1368
1652
|
const cwd = process.cwd();
|
|
1369
1653
|
const results = [];
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
package/src/mcp/server.ts
CHANGED
|
@@ -566,7 +566,7 @@ class CodeBakersServer {
|
|
|
566
566
|
{
|
|
567
567
|
name: 'scaffold_project',
|
|
568
568
|
description:
|
|
569
|
-
'Create a new project from scratch with Next.js + Supabase + Drizzle. Use this when user wants to build something new and no project exists yet. Creates all files, installs dependencies, and sets up CodeBakers patterns automatically.',
|
|
569
|
+
'Create a new project from scratch with Next.js + Supabase + Drizzle. Use this when user wants to build something new and no project exists yet. Creates all files, installs dependencies, and sets up CodeBakers patterns automatically. Set fullDeploy=true for seamless idea-to-deployment (creates GitHub repo, Supabase project, and deploys to Vercel). When fullDeploy=true, first call returns explanation - then call again with deployConfirmed=true after user confirms.',
|
|
570
570
|
inputSchema: {
|
|
571
571
|
type: 'object' as const,
|
|
572
572
|
properties: {
|
|
@@ -578,6 +578,14 @@ class CodeBakersServer {
|
|
|
578
578
|
type: 'string',
|
|
579
579
|
description: 'Brief description of what the project is for (used in PRD.md)',
|
|
580
580
|
},
|
|
581
|
+
fullDeploy: {
|
|
582
|
+
type: 'boolean',
|
|
583
|
+
description: 'If true, enables full deployment flow (GitHub + Supabase + Vercel). First call returns explanation for user confirmation.',
|
|
584
|
+
},
|
|
585
|
+
deployConfirmed: {
|
|
586
|
+
type: 'boolean',
|
|
587
|
+
description: 'Set to true AFTER user confirms they want full deployment. Only set this after showing user the explanation and getting their approval.',
|
|
588
|
+
},
|
|
581
589
|
},
|
|
582
590
|
required: ['projectName'],
|
|
583
591
|
},
|
|
@@ -900,7 +908,7 @@ class CodeBakersServer {
|
|
|
900
908
|
return this.handleGetPatternSection(args as { pattern: string; section: string });
|
|
901
909
|
|
|
902
910
|
case 'scaffold_project':
|
|
903
|
-
return this.handleScaffoldProject(args as { projectName: string; description?: string });
|
|
911
|
+
return this.handleScaffoldProject(args as { projectName: string; description?: string; fullDeploy?: boolean; deployConfirmed?: boolean });
|
|
904
912
|
|
|
905
913
|
case 'init_project':
|
|
906
914
|
return this.handleInitProject(args as { projectName?: string });
|
|
@@ -1376,10 +1384,15 @@ Show the user what their simple request was expanded into, then proceed with the
|
|
|
1376
1384
|
};
|
|
1377
1385
|
}
|
|
1378
1386
|
|
|
1379
|
-
private async handleScaffoldProject(args: { projectName: string; description?: string }) {
|
|
1380
|
-
const { projectName, description } = args;
|
|
1387
|
+
private async handleScaffoldProject(args: { projectName: string; description?: string; fullDeploy?: boolean; deployConfirmed?: boolean }) {
|
|
1388
|
+
const { projectName, description, fullDeploy, deployConfirmed } = args;
|
|
1381
1389
|
const cwd = process.cwd();
|
|
1382
1390
|
|
|
1391
|
+
// If fullDeploy requested but not confirmed, show explanation and ask for confirmation
|
|
1392
|
+
if (fullDeploy && !deployConfirmed) {
|
|
1393
|
+
return this.showFullDeployExplanation(projectName, description);
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1383
1396
|
// Check if directory has files
|
|
1384
1397
|
const files = fs.readdirSync(cwd);
|
|
1385
1398
|
const hasFiles = files.filter(f => !f.startsWith('.')).length > 0;
|
|
@@ -1533,14 +1546,22 @@ phase: setup
|
|
|
1533
1546
|
|
|
1534
1547
|
results.push('\n---\n');
|
|
1535
1548
|
results.push('## ✅ Project Created Successfully!\n');
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1549
|
+
|
|
1550
|
+
// If fullDeploy is enabled and confirmed, proceed with cloud deployment
|
|
1551
|
+
if (fullDeploy && deployConfirmed) {
|
|
1552
|
+
results.push('## 🚀 Starting Full Deployment...\n');
|
|
1553
|
+
const deployResults = await this.executeFullDeploy(projectName, cwd, description);
|
|
1554
|
+
results.push(...deployResults);
|
|
1555
|
+
} else {
|
|
1556
|
+
results.push('### Next Steps:\n');
|
|
1557
|
+
results.push('1. **Set up Supabase:** Go to https://supabase.com and create a free project');
|
|
1558
|
+
results.push('2. **Add credentials:** Copy your Supabase URL and anon key to `.env.local`');
|
|
1559
|
+
results.push('3. **Start building:** Just tell me what features you want!\n');
|
|
1560
|
+
results.push('### Example:\n');
|
|
1561
|
+
results.push('> "Add user authentication with email/password"');
|
|
1562
|
+
results.push('> "Create a dashboard with stats cards"');
|
|
1563
|
+
results.push('> "Build a todo list with CRUD operations"');
|
|
1564
|
+
}
|
|
1544
1565
|
|
|
1545
1566
|
} catch (error) {
|
|
1546
1567
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
@@ -1555,6 +1576,285 @@ phase: setup
|
|
|
1555
1576
|
};
|
|
1556
1577
|
}
|
|
1557
1578
|
|
|
1579
|
+
/**
|
|
1580
|
+
* Show explanation of what fullDeploy will do and ask for confirmation
|
|
1581
|
+
*/
|
|
1582
|
+
private showFullDeployExplanation(projectName: string, description?: string) {
|
|
1583
|
+
const explanation = `# 🚀 Full Deployment: ${projectName}
|
|
1584
|
+
|
|
1585
|
+
## What This Will Do
|
|
1586
|
+
|
|
1587
|
+
Full deployment creates a complete production-ready environment automatically:
|
|
1588
|
+
|
|
1589
|
+
### 1. 📁 Local Project
|
|
1590
|
+
- Create Next.js + Supabase + Drizzle project
|
|
1591
|
+
- Install all dependencies
|
|
1592
|
+
- Set up CodeBakers patterns
|
|
1593
|
+
|
|
1594
|
+
### 2. 🐙 GitHub Repository
|
|
1595
|
+
- Create a new private repository: \`${projectName}\`
|
|
1596
|
+
- Initialize git and push code
|
|
1597
|
+
- Set up .gitignore properly
|
|
1598
|
+
|
|
1599
|
+
### 3. 🗄️ Supabase Project
|
|
1600
|
+
- Create a new Supabase project
|
|
1601
|
+
- Get database connection string
|
|
1602
|
+
- Get API keys (anon + service role)
|
|
1603
|
+
- Auto-configure .env.local
|
|
1604
|
+
|
|
1605
|
+
### 4. 🔺 Vercel Deployment
|
|
1606
|
+
- Deploy to Vercel
|
|
1607
|
+
- Connect to GitHub for auto-deploys
|
|
1608
|
+
- Set all environment variables
|
|
1609
|
+
- Get your live URL
|
|
1610
|
+
|
|
1611
|
+
---
|
|
1612
|
+
|
|
1613
|
+
## Requirements
|
|
1614
|
+
|
|
1615
|
+
Make sure you have these CLIs installed and authenticated:
|
|
1616
|
+
- \`gh\` - GitHub CLI (run: \`gh auth login\`)
|
|
1617
|
+
- \`supabase\` - Supabase CLI (run: \`supabase login\`)
|
|
1618
|
+
- \`vercel\` - Vercel CLI (run: \`vercel login\`)
|
|
1619
|
+
|
|
1620
|
+
---
|
|
1621
|
+
|
|
1622
|
+
## 🎯 Result
|
|
1623
|
+
|
|
1624
|
+
After completion, you'll have:
|
|
1625
|
+
- ✅ GitHub repo with your code
|
|
1626
|
+
- ✅ Supabase project with database ready
|
|
1627
|
+
- ✅ Live URL on Vercel
|
|
1628
|
+
- ✅ Auto-deploys on every push
|
|
1629
|
+
|
|
1630
|
+
---
|
|
1631
|
+
|
|
1632
|
+
**⚠️ IMPORTANT: Ask the user to confirm before proceeding.**
|
|
1633
|
+
|
|
1634
|
+
To proceed, call \`scaffold_project\` again with:
|
|
1635
|
+
\`\`\`json
|
|
1636
|
+
{
|
|
1637
|
+
"projectName": "${projectName}",
|
|
1638
|
+
"description": "${description || ''}",
|
|
1639
|
+
"fullDeploy": true,
|
|
1640
|
+
"deployConfirmed": true
|
|
1641
|
+
}
|
|
1642
|
+
\`\`\`
|
|
1643
|
+
|
|
1644
|
+
Or if user declines, call without fullDeploy:
|
|
1645
|
+
\`\`\`json
|
|
1646
|
+
{
|
|
1647
|
+
"projectName": "${projectName}",
|
|
1648
|
+
"description": "${description || ''}"
|
|
1649
|
+
}
|
|
1650
|
+
\`\`\`
|
|
1651
|
+
`;
|
|
1652
|
+
|
|
1653
|
+
return {
|
|
1654
|
+
content: [{
|
|
1655
|
+
type: 'text' as const,
|
|
1656
|
+
text: explanation,
|
|
1657
|
+
}],
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
/**
|
|
1662
|
+
* Execute full cloud deployment (GitHub + Supabase + Vercel)
|
|
1663
|
+
*/
|
|
1664
|
+
private async executeFullDeploy(projectName: string, cwd: string, description?: string): Promise<string[]> {
|
|
1665
|
+
const results: string[] = [];
|
|
1666
|
+
|
|
1667
|
+
// Check for required CLIs
|
|
1668
|
+
const cliChecks = this.checkRequiredCLIs();
|
|
1669
|
+
if (cliChecks.missing.length > 0) {
|
|
1670
|
+
results.push('### ❌ Missing Required CLIs\n');
|
|
1671
|
+
results.push('The following CLIs are required for full deployment:\n');
|
|
1672
|
+
for (const cli of cliChecks.missing) {
|
|
1673
|
+
results.push(`- **${cli.name}**: ${cli.installCmd}`);
|
|
1674
|
+
}
|
|
1675
|
+
results.push('\nInstall the missing CLIs and try again.');
|
|
1676
|
+
return results;
|
|
1677
|
+
}
|
|
1678
|
+
results.push('✓ All required CLIs found\n');
|
|
1679
|
+
|
|
1680
|
+
// Step 1: Initialize Git and create GitHub repo
|
|
1681
|
+
results.push('### Step 1: GitHub Repository\n');
|
|
1682
|
+
try {
|
|
1683
|
+
// Initialize git
|
|
1684
|
+
execSync('git init', { cwd, stdio: 'pipe' });
|
|
1685
|
+
execSync('git add .', { cwd, stdio: 'pipe' });
|
|
1686
|
+
execSync('git commit -m "Initial commit from CodeBakers"', { cwd, stdio: 'pipe' });
|
|
1687
|
+
results.push('✓ Initialized git repository');
|
|
1688
|
+
|
|
1689
|
+
// Create GitHub repo
|
|
1690
|
+
const ghDescription = description || `${projectName} - Created with CodeBakers`;
|
|
1691
|
+
execSync(`gh repo create ${projectName} --private --source=. --push --description "${ghDescription}"`, { cwd, stdio: 'pipe' });
|
|
1692
|
+
results.push(`✓ Created GitHub repo: ${projectName}`);
|
|
1693
|
+
results.push(` → https://github.com/${this.getGitHubUsername()}/${projectName}\n`);
|
|
1694
|
+
} catch (error) {
|
|
1695
|
+
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
1696
|
+
results.push(`⚠️ GitHub setup failed: ${msg}`);
|
|
1697
|
+
results.push(' You can create the repo manually: gh repo create\n');
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
// Step 2: Create Supabase project
|
|
1701
|
+
results.push('### Step 2: Supabase Project\n');
|
|
1702
|
+
try {
|
|
1703
|
+
// Create Supabase project (this may take a while)
|
|
1704
|
+
const orgId = this.getSupabaseOrgId();
|
|
1705
|
+
if (orgId) {
|
|
1706
|
+
execSync(`supabase projects create ${projectName} --org-id ${orgId} --region us-east-1 --db-password "${this.generatePassword()}"`, { cwd, stdio: 'pipe', timeout: 120000 });
|
|
1707
|
+
results.push(`✓ Created Supabase project: ${projectName}`);
|
|
1708
|
+
|
|
1709
|
+
// Get project credentials
|
|
1710
|
+
const projectsOutput = execSync('supabase projects list --output json', { cwd, encoding: 'utf-8' });
|
|
1711
|
+
const projects = JSON.parse(projectsOutput);
|
|
1712
|
+
const newProject = projects.find((p: { name: string }) => p.name === projectName);
|
|
1713
|
+
|
|
1714
|
+
if (newProject) {
|
|
1715
|
+
// Update .env.local with Supabase credentials
|
|
1716
|
+
const envPath = path.join(cwd, '.env.local');
|
|
1717
|
+
let envContent = fs.readFileSync(envPath, 'utf-8');
|
|
1718
|
+
envContent = envContent.replace('your-supabase-url', `https://${newProject.id}.supabase.co`);
|
|
1719
|
+
envContent = envContent.replace('your-anon-key', newProject.anon_key || 'YOUR_ANON_KEY');
|
|
1720
|
+
fs.writeFileSync(envPath, envContent);
|
|
1721
|
+
results.push('✓ Updated .env.local with Supabase credentials\n');
|
|
1722
|
+
}
|
|
1723
|
+
} else {
|
|
1724
|
+
results.push('⚠️ Could not detect Supabase organization');
|
|
1725
|
+
results.push(' Run: supabase orgs list\n');
|
|
1726
|
+
}
|
|
1727
|
+
} catch (error) {
|
|
1728
|
+
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
1729
|
+
results.push(`⚠️ Supabase setup failed: ${msg}`);
|
|
1730
|
+
results.push(' Create project manually at: https://supabase.com/dashboard\n');
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
// Step 3: Deploy to Vercel
|
|
1734
|
+
results.push('### Step 3: Vercel Deployment\n');
|
|
1735
|
+
try {
|
|
1736
|
+
// Link to Vercel (creates new project)
|
|
1737
|
+
execSync('vercel link --yes', { cwd, stdio: 'pipe' });
|
|
1738
|
+
results.push('✓ Linked to Vercel');
|
|
1739
|
+
|
|
1740
|
+
// Set environment variables from .env.local
|
|
1741
|
+
const envPath = path.join(cwd, '.env.local');
|
|
1742
|
+
if (fs.existsSync(envPath)) {
|
|
1743
|
+
const envContent = fs.readFileSync(envPath, 'utf-8');
|
|
1744
|
+
const envVars = envContent.split('\n')
|
|
1745
|
+
.filter(line => line.includes('=') && !line.startsWith('#'))
|
|
1746
|
+
.map(line => {
|
|
1747
|
+
const [key, ...valueParts] = line.split('=');
|
|
1748
|
+
return { key: key.trim(), value: valueParts.join('=').trim() };
|
|
1749
|
+
});
|
|
1750
|
+
|
|
1751
|
+
for (const { key, value } of envVars) {
|
|
1752
|
+
if (value && !value.includes('your-')) {
|
|
1753
|
+
try {
|
|
1754
|
+
execSync(`vercel env add ${key} production <<< "${value}"`, { cwd, stdio: 'pipe', shell: 'bash' });
|
|
1755
|
+
} catch {
|
|
1756
|
+
// Env var might already exist, try to update
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
results.push('✓ Set environment variables');
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
// Deploy to production
|
|
1764
|
+
const deployOutput = execSync('vercel --prod --yes', { cwd, encoding: 'utf-8' });
|
|
1765
|
+
const urlMatch = deployOutput.match(/https:\/\/[^\s]+\.vercel\.app/);
|
|
1766
|
+
const deployUrl = urlMatch ? urlMatch[0] : 'Check Vercel dashboard';
|
|
1767
|
+
results.push(`✓ Deployed to Vercel`);
|
|
1768
|
+
results.push(` → ${deployUrl}\n`);
|
|
1769
|
+
|
|
1770
|
+
// Connect to GitHub for auto-deploys
|
|
1771
|
+
try {
|
|
1772
|
+
execSync('vercel git connect --yes', { cwd, stdio: 'pipe' });
|
|
1773
|
+
results.push('✓ Connected to GitHub for auto-deploys\n');
|
|
1774
|
+
} catch {
|
|
1775
|
+
results.push('⚠️ Could not auto-connect to GitHub\n');
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
} catch (error) {
|
|
1779
|
+
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
1780
|
+
results.push(`⚠️ Vercel deployment failed: ${msg}`);
|
|
1781
|
+
results.push(' Deploy manually: vercel --prod\n');
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// Summary
|
|
1785
|
+
results.push('---\n');
|
|
1786
|
+
results.push('## 🎉 Full Deployment Complete!\n');
|
|
1787
|
+
results.push('Your project is now live with:');
|
|
1788
|
+
results.push('- GitHub repo with CI/CD ready');
|
|
1789
|
+
results.push('- Supabase database configured');
|
|
1790
|
+
results.push('- Vercel hosting with auto-deploys\n');
|
|
1791
|
+
results.push('**Start building features - every push auto-deploys!**');
|
|
1792
|
+
|
|
1793
|
+
return results;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
/**
|
|
1797
|
+
* Check if required CLIs are installed
|
|
1798
|
+
*/
|
|
1799
|
+
private checkRequiredCLIs(): { installed: string[]; missing: { name: string; installCmd: string }[] } {
|
|
1800
|
+
const clis = [
|
|
1801
|
+
{ name: 'gh', cmd: 'gh --version', installCmd: 'npm install -g gh' },
|
|
1802
|
+
{ name: 'supabase', cmd: 'supabase --version', installCmd: 'npm install -g supabase' },
|
|
1803
|
+
{ name: 'vercel', cmd: 'vercel --version', installCmd: 'npm install -g vercel' },
|
|
1804
|
+
];
|
|
1805
|
+
|
|
1806
|
+
const installed: string[] = [];
|
|
1807
|
+
const missing: { name: string; installCmd: string }[] = [];
|
|
1808
|
+
|
|
1809
|
+
for (const cli of clis) {
|
|
1810
|
+
try {
|
|
1811
|
+
execSync(cli.cmd, { stdio: 'pipe' });
|
|
1812
|
+
installed.push(cli.name);
|
|
1813
|
+
} catch {
|
|
1814
|
+
missing.push({ name: cli.name, installCmd: cli.installCmd });
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
return { installed, missing };
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
/**
|
|
1822
|
+
* Get GitHub username from gh CLI
|
|
1823
|
+
*/
|
|
1824
|
+
private getGitHubUsername(): string {
|
|
1825
|
+
try {
|
|
1826
|
+
const output = execSync('gh api user --jq .login', { encoding: 'utf-8' });
|
|
1827
|
+
return output.trim();
|
|
1828
|
+
} catch {
|
|
1829
|
+
return 'YOUR_USERNAME';
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
/**
|
|
1834
|
+
* Get Supabase organization ID
|
|
1835
|
+
*/
|
|
1836
|
+
private getSupabaseOrgId(): string | null {
|
|
1837
|
+
try {
|
|
1838
|
+
const output = execSync('supabase orgs list --output json', { encoding: 'utf-8' });
|
|
1839
|
+
const orgs = JSON.parse(output);
|
|
1840
|
+
return orgs[0]?.id || null;
|
|
1841
|
+
} catch {
|
|
1842
|
+
return null;
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
/**
|
|
1847
|
+
* Generate a secure random password for Supabase
|
|
1848
|
+
*/
|
|
1849
|
+
private generatePassword(): string {
|
|
1850
|
+
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
1851
|
+
let password = '';
|
|
1852
|
+
for (let i = 0; i < 24; i++) {
|
|
1853
|
+
password += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
1854
|
+
}
|
|
1855
|
+
return password;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1558
1858
|
private async handleInitProject(args: { projectName?: string }) {
|
|
1559
1859
|
const cwd = process.cwd();
|
|
1560
1860
|
const results: string[] = [];
|