UNPKG

pame-core-cli

Version:

PAME.AI Core Operating System CLI - Open Source AI Platform for Agentic Commerce

308 lines • 13.4 kB
import { Command } from 'commander'; import chalk from 'chalk'; import ora from 'ora'; import inquirer from 'inquirer'; import { GoDaddyManager } from '../services/godaddy-manager.js'; import { VercelManager } from '../services/vercel-manager.js'; import { execSync } from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; export function createDNSCommand() { const dns = new Command('dns') .description('Manage DNS records for PAME.AI subdomains') .addHelpText('after', ` Examples: $ pame-core dns add api --vercel-url pame-api-xxxxx.vercel.app $ pame-core dns add crm --auto-create $ pame-core dns list $ pame-core dns verify api `); dns .command('add <subdomain>') .description('Add DNS records for a subdomain') .option('--vercel-url <url>', 'Vercel deployment URL') .option('--auto-create', 'Automatically create and deploy project') .option('--template <template>', 'Project template to use', 'nextjs') .option('--skip-deploy', 'Skip deployment step') .action(async (subdomain, options) => { const spinner = ora(); try { console.log(chalk.cyan(`🌐 Setting up ${subdomain}.pame.ai...\n`)); const godaddy = new GoDaddyManager(); const vercel = new VercelManager(); let vercelUrl = options.vercelUrl; // Step 1: Auto-create project if requested if (options.autoCreate && !options.skipDeploy) { spinner.start('Creating Vercel project...'); const projectName = `pame-${subdomain}`; const projectPath = path.join(process.cwd(), projectName); // Create project directory if (!fs.existsSync(projectPath)) { fs.mkdirSync(projectPath, { recursive: true }); } // Initialize project process.chdir(projectPath); spinner.text = 'Initializing Next.js project...'; execSync('npx create-next-app@latest . --typescript --tailwind --app --yes', { stdio: 'ignore' }); // Create basic page const pageContent = `export default function ${capitalize(subdomain)}Page() { return ( <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100"> <div className="text-center"> <h1 className="text-4xl font-bold text-gray-800 mb-4"> ${subdomain}.pame.ai </h1> <p className="text-xl text-gray-600"> Coming soon... </p> </div> </div> ); }`; fs.writeFileSync('app/page.tsx', pageContent); spinner.text = 'Linking to Vercel...'; // Link to Vercel try { execSync(`vercel link --yes --project ${projectName}`, { stdio: 'ignore' }); } catch { // Project might not exist, create it execSync('vercel link --yes', { stdio: 'pipe', input: `y\n${projectName}\n` }); } spinner.text = 'Deploying to Vercel...'; // Deploy to production const deployOutput = execSync('vercel --prod --yes', { encoding: 'utf8' }); // Extract deployment URL const urlMatch = deployOutput.match(/https:\/\/[a-z0-9-]+\.vercel\.app/); if (urlMatch) { vercelUrl = urlMatch[0]; spinner.succeed(`Deployed to ${vercelUrl}`); } else { spinner.fail('Could not extract deployment URL'); } // Return to original directory process.chdir('..'); } // Step 2: Add custom domain in Vercel if (!vercelUrl) { const answers = await inquirer.prompt([ { type: 'input', name: 'vercelUrl', message: 'Enter your Vercel deployment URL:', validate: (input) => { return input.includes('.vercel.app') || 'Please enter a valid Vercel URL'; } } ]); vercelUrl = answers.vercelUrl; } spinner.start(`Adding ${subdomain}.pame.ai to Vercel project...`); // Extract project name from URL const projectName = vercelUrl.replace('https://', '').split('-')[0]; // Add domain via Vercel API (if available) // For now, we'll provide instructions spinner.info('Please add the domain manually in Vercel dashboard'); console.log(chalk.yellow('\nšŸ“‹ Manual steps required:')); console.log(chalk.gray('1. Go to https://vercel.com/getaifactory/' + projectName + '/settings/domains')); console.log(chalk.gray(`2. Click "Add Domain"`)); console.log(chalk.gray(`3. Enter: ${subdomain}.pame.ai`)); console.log(chalk.gray('4. Copy the DNS records shown\n')); // Step 3: Get DNS records from user const { hasDNSRecords } = await inquirer.prompt([ { type: 'confirm', name: 'hasDNSRecords', message: 'Have you copied the DNS records from Vercel?', default: false } ]); if (!hasDNSRecords) { console.log(chalk.red('Please complete the Vercel setup first.')); return; } // Get DNS records const dnsAnswers = await inquirer.prompt([ { type: 'input', name: 'cnameValue', message: 'Enter the CNAME value (e.g., 8af789697b12dc37.vercel-dns-016.com):', validate: (input) => { return input.includes('vercel-dns') || 'Please enter a valid Vercel DNS value'; } }, { type: 'input', name: 'txtValue', message: 'Enter the TXT verification value (e.g., vc-domain-verify=...):', validate: (input) => { return input.includes('vc-domain-verify') || 'Please enter a valid verification value'; } } ]); // Step 4: Add DNS records to GoDaddy spinner.start('Adding DNS records to GoDaddy...'); const records = [ { type: 'CNAME', name: subdomain, data: dnsAnswers.cnameValue, ttl: 600 }, { type: 'TXT', name: '_vercel', data: dnsAnswers.txtValue, ttl: 600 } ]; try { await godaddy.addRecords(records); spinner.succeed('DNS records added successfully!'); console.log(chalk.green('\nāœ… Setup complete!')); console.log(chalk.blue('\nšŸ” DNS Records Added:')); records.forEach(record => { console.log(chalk.gray(` ${record.type} ${record.name} → ${record.data}`)); }); console.log(chalk.yellow('\nā±ļø Next steps:')); console.log(chalk.gray('1. Wait 5-15 minutes for DNS propagation')); console.log(chalk.gray('2. Click "Refresh" in Vercel domain settings')); console.log(chalk.gray(`3. Your subdomain will be live at https://${subdomain}.pame.ai`)); } catch (error) { spinner.fail('Failed to add DNS records'); console.error(chalk.red('Error:'), error.message); process.exit(1); } } catch (error) { spinner.fail('Setup failed'); console.error(chalk.red('Error:'), error.message); process.exit(1); } }); dns .command('list') .description('List all DNS records for pame.ai') .action(async () => { const spinner = ora('Fetching DNS records...').start(); try { const godaddy = new GoDaddyManager(); const records = await godaddy.getAllRecords(); spinner.succeed(`Found ${records.length} DNS records`); // Group by type const grouped = records.reduce((acc, record) => { if (!acc[record.type]) acc[record.type] = []; acc[record.type].push(record); return acc; }, {}); // Display records Object.entries(grouped).forEach(([type, typeRecords]) => { console.log(chalk.cyan(`\n${type} Records:`)); typeRecords.forEach(record => { const name = record.name === '@' ? 'pame.ai' : `${record.name}.pame.ai`; console.log(chalk.gray(` ${name} → ${record.data} (TTL: ${record.ttl}s)`)); }); }); } catch (error) { spinner.fail('Failed to fetch DNS records'); console.error(chalk.red('Error:'), error.message); process.exit(1); } }); dns .command('verify <subdomain>') .description('Check if a subdomain is properly configured') .action(async (subdomain) => { const spinner = ora('Checking DNS configuration...').start(); try { const godaddy = new GoDaddyManager(); const records = await godaddy.getAllRecords(); // Check for CNAME record const cnameRecord = records.find(r => r.type === 'CNAME' && r.name === subdomain); // Check for TXT verification record const txtRecord = records.find(r => r.type === 'TXT' && r.name === '_vercel' && r.data.includes(`${subdomain}.pame.ai`)); spinner.stop(); console.log(chalk.cyan(`\nšŸ” DNS Status for ${subdomain}.pame.ai:\n`)); if (cnameRecord) { console.log(chalk.green('āœ… CNAME Record Found:')); console.log(chalk.gray(` ${subdomain} → ${cnameRecord.data}`)); } else { console.log(chalk.red('āŒ CNAME Record Missing')); } if (txtRecord) { console.log(chalk.green('āœ… TXT Verification Record Found:')); console.log(chalk.gray(` _vercel → ${txtRecord.data}`)); } else { console.log(chalk.yellow('āš ļø TXT Verification Record Missing')); console.log(chalk.gray(' (This may be normal if domain is already verified)')); } // Test DNS resolution console.log(chalk.cyan('\n🌐 DNS Resolution Test:')); try { const { execSync } = require('child_process'); const result = execSync(`nslookup ${subdomain}.pame.ai`, { encoding: 'utf8' }); if (result.includes('Non-authoritative answer')) { console.log(chalk.green('āœ… Domain resolves successfully')); } else { console.log(chalk.yellow('āš ļø Domain resolution unclear')); } } catch { console.log(chalk.red('āŒ Domain does not resolve')); } // Test HTTPS console.log(chalk.cyan('\nšŸ”’ HTTPS Test:')); try { const https = require('https'); await new Promise((resolve, reject) => { https.get(`https://${subdomain}.pame.ai`, (res) => { if (res.statusCode === 200 || res.statusCode === 308) { console.log(chalk.green('āœ… HTTPS is working')); resolve(true); } else { console.log(chalk.yellow(`āš ļø HTTP Status: ${res.statusCode}`)); resolve(false); } }).on('error', reject); }); } catch (error) { if (error.code === 'ENOTFOUND') { console.log(chalk.red('āŒ Domain not found (DNS may still be propagating)')); } else { console.log(chalk.red('āŒ HTTPS connection failed')); } } } catch (error) { spinner.fail('Verification failed'); console.error(chalk.red('Error:'), error.message); process.exit(1); } }); return dns; } function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } //# sourceMappingURL=dns.js.map