UNPKG

forge-deploy-cli

Version:

Professional CLI for local deployments with automatic subdomain routing, SSL certificates, and infrastructure management

551 lines 24.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.infoCommand = infoCommand; const chalk_1 = __importDefault(require("chalk")); const fs_extra_1 = __importDefault(require("fs-extra")); const child_process_1 = require("child_process"); const config_1 = require("../services/config"); const api_1 = require("../services/api"); const localDeployment_1 = require("../services/localDeployment"); const system_1 = require("../utils/system"); async function infoCommand(options) { try { if (options.local) { await showLocalDeploymentInfo(options); } else { await showRemoteDeploymentInfo(options); } } catch (error) { console.error(chalk_1.default.red('Failed to fetch deployment info:'), error instanceof Error ? error.message : error); process.exit(1); } } async function showLocalDeploymentInfo(options) { const deployments = await localDeployment_1.LocalDeploymentManager.listDeployments(); if (options.deploymentId) { const deployment = deployments.find(d => d.id === options.deploymentId); if (!deployment) { console.error(chalk_1.default.red(`Local deployment ${options.deploymentId} not found`)); process.exit(1); } await displayDeploymentInfo(deployment, options.json); } else { if (deployments.length === 0) { console.log(chalk_1.default.yellow('No local deployments found')); return; } console.log(chalk_1.default.blue.bold('Local Deployments:')); deployments.forEach(deployment => { console.log(chalk_1.default.cyan(` ${deployment.id} - ${deployment.projectName} (${deployment.status})`)); }); console.log(); console.log(chalk_1.default.gray('Use: forge info --local --deployment-id <deployment-id> for detailed information')); } } async function showRemoteDeploymentInfo(options) { const configService = new config_1.ConfigService(); const config = await configService.getConfig(); if (!config.apiKey) { console.error(chalk_1.default.red('Not logged in. Please run: forge login')); process.exit(1); } const apiService = new api_1.ForgeApiService(config.apiUrl); apiService.setApiKey(config.apiKey); let deploymentId = options.deploymentId; if (!deploymentId) { deploymentId = config.deploymentId; if (!deploymentId) { console.error(chalk_1.default.red('No deployment ID provided and no current project deployment found')); console.log(chalk_1.default.gray('Use: forge info --deployment-id <deployment-id>')); process.exit(1); } } const response = await apiService.getDeployments({ id: deploymentId }); if (!response.success) { throw new Error(response.error?.message || 'Failed to fetch deployment'); } const deployments = response.data?.deployments || []; if (deployments.length === 0) { console.error(chalk_1.default.red(`Deployment ${deploymentId} not found`)); process.exit(1); } await displayDeploymentInfo(deployments[0], options.json); } /** * Check SSL certificate status by examining nginx config and certificate files */ async function checkSSLStatus(subdomain) { try { const nginxConfigPath = (0, system_1.isWindows)() ? `C:\\\\nginx\\\\conf\\\\sites-available\\\\${subdomain}.conf` : `/etc/nginx/sites-available/${subdomain}.conf`; // Check if nginx config exists and has SSL configuration if (!await fs_extra_1.default.pathExists(nginxConfigPath)) { return { enabled: false }; } const nginxConfig = await fs_extra_1.default.readFile(nginxConfigPath, 'utf8'); // Check for SSL configuration in nginx const hasSSLConfig = nginxConfig.includes('ssl_certificate') && nginxConfig.includes('ssl_certificate_key'); if (!hasSSLConfig) { return { enabled: false }; } // Extract certificate path from nginx config const certPathMatch = nginxConfig.match(/ssl_certificate\\s+([^;]+);/); const certPath = certPathMatch ? certPathMatch[1].trim().replace(/['"]/g, '') : null; if (!certPath || !await fs_extra_1.default.pathExists(certPath)) { return { enabled: true, certificate: 'Configuration found but certificate file missing' }; } // Get certificate information using openssl try { const certInfo = (0, child_process_1.execSync)(`openssl x509 -in "${certPath}" -text -noout`, { encoding: 'utf8', timeout: 5000 }); const notAfterMatch = certInfo.match(/Not After : (.+)/); const notBeforeMatch = certInfo.match(/Not Before: (.+)/); const issuerMatch = certInfo.match(/Issuer: (.+)/); let expiresAt, validFrom, issuer, daysUntilExpiry; if (notAfterMatch) { expiresAt = new Date(notAfterMatch[1]).toISOString(); daysUntilExpiry = Math.ceil((new Date(notAfterMatch[1]).getTime() - Date.now()) / (1000 * 60 * 60 * 24)); } if (notBeforeMatch) { validFrom = new Date(notBeforeMatch[1]).toISOString(); } if (issuerMatch) { issuer = issuerMatch[1].trim(); } return { enabled: true, certificate: certPath, expiresAt, validFrom, issuer, daysUntilExpiry }; } catch (opensslError) { // If openssl fails, try alternative method try { const certInfo = (0, child_process_1.execSync)(`openssl x509 -in "${certPath}" -enddate -issuer -startdate -noout`, { encoding: 'utf8', timeout: 5000 }); const lines = certInfo.split('\\n'); let expiresAt, validFrom, issuer, daysUntilExpiry; for (const line of lines) { if (line.startsWith('notAfter=')) { const dateStr = line.replace('notAfter=', ''); expiresAt = new Date(dateStr).toISOString(); daysUntilExpiry = Math.ceil((new Date(dateStr).getTime() - Date.now()) / (1000 * 60 * 60 * 24)); } else if (line.startsWith('notBefore=')) { const dateStr = line.replace('notBefore=', ''); validFrom = new Date(dateStr).toISOString(); } else if (line.startsWith('issuer=')) { issuer = line.replace('issuer=', ''); } } return { enabled: true, certificate: certPath, expiresAt, validFrom, issuer, daysUntilExpiry }; } catch (fallbackError) { return { enabled: true, certificate: certPath, expiresAt: 'Unable to read certificate details', }; } } } catch (error) { // Fallback: check if HTTPS is working by making a request try { const domain = `${subdomain}.forgecli.tech`; const response = await fetch(`https://${domain}`, { signal: AbortSignal.timeout(5000), method: 'HEAD' }); return { enabled: response.ok, certificate: 'SSL working but certificate details unavailable' }; } catch { return { enabled: false }; } } } /** * Get enhanced system metrics */ async function getSystemMetrics() { try { if ((0, system_1.isWindows)()) { return await getWindowsMetrics(); } else { return await getUnixMetrics(); } } catch (error) { console.warn(chalk_1.default.yellow('Warning: Could not fetch system metrics')); return { cpu: 0, memory: { used: 0, total: 0, percentage: 0 }, disk: { used: 0, total: 0, percentage: 0 }, uptime: 0 }; } } async function getWindowsMetrics() { try { // Get CPU usage const cpuInfo = (0, child_process_1.execSync)('wmic cpu get loadpercentage /value', { encoding: 'utf8', timeout: 5000 }); const cpuMatch = cpuInfo.match(/LoadPercentage=(\\d+)/); const cpu = cpuMatch ? parseInt(cpuMatch[1]) : 0; // Get memory info const memInfo = (0, child_process_1.execSync)('wmic computersystem get TotalPhysicalMemory /value', { encoding: 'utf8', timeout: 5000 }); const memAvail = (0, child_process_1.execSync)('wmic OS get AvailablePhysicalMemory /value', { encoding: 'utf8', timeout: 5000 }); const totalMatch = memInfo.match(/TotalPhysicalMemory=(\\d+)/); const availMatch = memAvail.match(/AvailablePhysicalMemory=(\\d+)/); const totalMemory = totalMatch ? parseInt(totalMatch[1]) : 0; const availableMemory = availMatch ? parseInt(availMatch[1]) * 1024 : 0; // Convert KB to bytes const usedMemory = totalMemory - availableMemory; const memoryPercentage = totalMemory > 0 ? (usedMemory / totalMemory) * 100 : 0; // Get disk info (C: drive) const diskInfo = (0, child_process_1.execSync)('wmic logicaldisk where caption="C:" get size,freespace /value', { encoding: 'utf8', timeout: 5000 }); const diskSizeMatch = diskInfo.match(/Size=(\\d+)/); const diskFreeMatch = diskInfo.match(/FreeSpace=(\\d+)/); const diskTotal = diskSizeMatch ? parseInt(diskSizeMatch[1]) : 0; const diskFree = diskFreeMatch ? parseInt(diskFreeMatch[1]) : 0; const diskUsed = diskTotal - diskFree; const diskPercentage = diskTotal > 0 ? (diskUsed / diskTotal) * 100 : 0; // Get uptime const uptimeInfo = (0, child_process_1.execSync)('wmic os get lastbootuptime /value', { encoding: 'utf8', timeout: 5000 }); const bootTimeMatch = uptimeInfo.match(/LastBootUpTime=(\\d{14})/); let uptime = 0; if (bootTimeMatch) { const bootTimeStr = bootTimeMatch[1]; const bootYear = parseInt(bootTimeStr.substr(0, 4)); const bootMonth = parseInt(bootTimeStr.substr(4, 2)) - 1; const bootDay = parseInt(bootTimeStr.substr(6, 2)); const bootHour = parseInt(bootTimeStr.substr(8, 2)); const bootMin = parseInt(bootTimeStr.substr(10, 2)); const bootSec = parseInt(bootTimeStr.substr(12, 2)); const bootTime = new Date(bootYear, bootMonth, bootDay, bootHour, bootMin, bootSec); uptime = Math.floor((Date.now() - bootTime.getTime()) / 1000); } return { cpu, memory: { used: usedMemory, total: totalMemory, percentage: memoryPercentage }, disk: { used: diskUsed, total: diskTotal, percentage: diskPercentage }, uptime }; } catch (error) { throw new Error(`Windows metrics failed: ${error}`); } } async function getUnixMetrics() { try { // Get CPU usage from top const cpuInfo = (0, child_process_1.execSync)("top -bn1 | grep 'Cpu(s)' | awk '{print $2}' | sed 's/%us,//'", { encoding: 'utf8', timeout: 5000 }); const cpu = parseFloat(cpuInfo.trim()) || 0; // Get memory info const memInfo = (0, child_process_1.execSync)('free -b', { encoding: 'utf8', timeout: 5000 }); const memLines = memInfo.split('\\n'); const memLine = memLines.find(line => line.startsWith('Mem:')); let totalMemory = 0, usedMemory = 0, memoryPercentage = 0; if (memLine) { const memParts = memLine.split(/\\s+/); totalMemory = parseInt(memParts[1]) || 0; usedMemory = parseInt(memParts[2]) || 0; memoryPercentage = totalMemory > 0 ? (usedMemory / totalMemory) * 100 : 0; } // Get disk info const diskInfo = (0, child_process_1.execSync)('df -B1 /', { encoding: 'utf8', timeout: 5000 }); const diskLines = diskInfo.split('\\n'); const diskLine = diskLines[1]; // Second line has the data let diskTotal = 0, diskUsed = 0, diskPercentage = 0; if (diskLine) { const diskParts = diskLine.split(/\\s+/); diskTotal = parseInt(diskParts[1]) || 0; diskUsed = parseInt(diskParts[2]) || 0; diskPercentage = diskTotal > 0 ? (diskUsed / diskTotal) * 100 : 0; } // Get uptime const uptimeInfo = (0, child_process_1.execSync)('cat /proc/uptime', { encoding: 'utf8', timeout: 5000 }); const uptime = parseFloat(uptimeInfo.split(' ')[0]) || 0; // Get load average const loadInfo = (0, child_process_1.execSync)('cat /proc/loadavg', { encoding: 'utf8', timeout: 5000 }); const loadParts = loadInfo.split(' '); const loadAverage = [ parseFloat(loadParts[0]) || 0, parseFloat(loadParts[1]) || 0, parseFloat(loadParts[2]) || 0 ]; return { cpu, memory: { used: usedMemory, total: totalMemory, percentage: memoryPercentage }, disk: { used: diskUsed, total: diskTotal, percentage: diskPercentage }, uptime: Math.floor(uptime), loadAverage }; } catch (error) { throw new Error(`Unix metrics failed: ${error}`); } } async function displayDeploymentInfo(deployment, jsonOutput = false) { // Update resources for local deployments if (deployment.port && deployment.id) { await localDeployment_1.LocalDeploymentManager.updateDeploymentResources(deployment.id); // Refresh deployment data after resource update const updatedDeployment = await localDeployment_1.LocalDeploymentManager.getDeployment(deployment.id); if (updatedDeployment) { deployment = updatedDeployment; } } // Get enhanced system metrics const systemMetrics = await getSystemMetrics(); // Get SSL status for the subdomain const subdomain = deployment.subdomain || deployment.id?.substring(0, 8); const sslStatus = await checkSSLStatus(subdomain); // Get additional health info for local deployments let healthInfo = null; if (deployment.port) { try { const startTime = Date.now(); const response = await fetch(`http://localhost:${deployment.port}/health`, { signal: AbortSignal.timeout(5000) }); const responseTime = Date.now() - startTime; healthInfo = { status: response.ok ? 'healthy' : 'unhealthy', responseTime, lastCheck: new Date().toISOString() }; } catch { healthInfo = { status: 'unhealthy', responseTime: 0, lastCheck: new Date().toISOString() }; } } const info = { id: deployment.id, projectName: deployment.projectName, subdomain, framework: deployment.framework, status: deployment.status, url: deployment.url, uptime: deployment.startedAt ? calculateUptime(deployment.startedAt) : 'N/A', lastUpdated: deployment.updatedAt || deployment.startedAt || new Date().toISOString(), health: healthInfo || { status: deployment.healthStatus || 'unknown', responseTime: 0, lastCheck: new Date().toISOString() }, resources: { cpu: deployment.resources?.cpu || systemMetrics.cpu, memory: deployment.resources?.memory || systemMetrics.memory.percentage, memoryUsed: systemMetrics.memory.used, memoryTotal: systemMetrics.memory.total, diskUsed: deployment.resources?.diskUsed || systemMetrics.disk.used, diskTotal: systemMetrics.disk.total, diskUsagePercent: deployment.resources?.diskUsagePercent || systemMetrics.disk.percentage, }, system: { uptime: systemMetrics.uptime, loadAverage: systemMetrics.loadAverage }, ssl: sslStatus }; if (jsonOutput) { console.log(JSON.stringify(info, null, 2)); return; } // Display formatted output console.log(); console.log(chalk_1.default.blue.bold('Deployment Information')); console.log(chalk_1.default.gray('='.repeat(50))); console.log(); console.log(chalk_1.default.white.bold('Basic Information:')); console.log(` ${chalk_1.default.cyan('ID:')} ${info.id}`); console.log(` ${chalk_1.default.cyan('Project:')} ${info.projectName}`); console.log(` ${chalk_1.default.cyan('Framework:')} ${info.framework}`); console.log(` ${chalk_1.default.cyan('Status:')} ${getStatusBadge(info.status)}`); console.log(` ${chalk_1.default.cyan('Uptime:')} ${info.uptime}`); console.log(); console.log(chalk_1.default.white.bold('Access Information:')); console.log(` ${chalk_1.default.cyan('URL:')} ${chalk_1.default.blue.underline(info.url)}`); console.log(` ${chalk_1.default.cyan('Subdomain:')} ${info.subdomain}`); if (deployment.port) { console.log(` ${chalk_1.default.cyan('Local Port:')} ${deployment.port}`); } console.log(); console.log(chalk_1.default.white.bold('Health Status:')); console.log(` ${chalk_1.default.cyan('Health:')} ${getHealthBadge(info.health.status)}`); console.log(` ${chalk_1.default.cyan('Response Time:')} ${info.health.responseTime}ms`); console.log(` ${chalk_1.default.cyan('Last Check:')} ${formatDate(info.health.lastCheck)}`); console.log(); console.log(chalk_1.default.white.bold('Resource Usage:')); console.log(` ${chalk_1.default.cyan('CPU:')} ${getUsageBar(info.resources.cpu || 0)} ${(info.resources.cpu || 0).toFixed(1)}%`); if (info.resources.memoryTotal > 0) { const memoryGB = (info.resources.memoryUsed / (1024 * 1024 * 1024)).toFixed(2); const memoryTotalGB = (info.resources.memoryTotal / (1024 * 1024 * 1024)).toFixed(2); console.log(` ${chalk_1.default.cyan('Memory:')} ${getUsageBar(info.resources.memory || 0)} ${memoryGB}GB / ${memoryTotalGB}GB (${(info.resources.memory || 0).toFixed(1)}%)`); } else { console.log(` ${chalk_1.default.cyan('Memory:')} ${getUsageBar(info.resources.memory || 0)} ${(info.resources.memory || 0).toFixed(1)}%`); } if (info.resources.diskUsed !== undefined) { const diskLimitGB = deployment.storageLimit ? (deployment.storageLimit / (1024 * 1024 * 1024)) : 15; const diskUsedGB = info.resources.diskUsed / (1024 * 1024 * 1024); const diskPercent = info.resources.diskUsagePercent || 0; console.log(` ${chalk_1.default.cyan('Disk:')} ${getUsageBar(diskPercent)} ${diskUsedGB.toFixed(2)}GB / ${diskLimitGB}GB (${diskPercent.toFixed(1)}%)`); } else { console.log(` ${chalk_1.default.cyan('Disk:')} ${chalk_1.default.gray('N/A')}`); } console.log(); console.log(chalk_1.default.white.bold('System Information:')); console.log(` ${chalk_1.default.cyan('System Uptime:')} ${formatUptime(info.system.uptime)}`); if (info.system.loadAverage) { console.log(` ${chalk_1.default.cyan('Load Average:')} ${info.system.loadAverage.map((l) => l.toFixed(2)).join(', ')}`); } console.log(); console.log(chalk_1.default.white.bold('SSL Certificate:')); console.log(` ${chalk_1.default.cyan('Enabled:')} ${info.ssl.enabled ? chalk_1.default.green('✓ Yes') : chalk_1.default.red('✗ No')}`); if (info.ssl.enabled) { if (info.ssl.certificate && info.ssl.certificate !== 'Unable to read certificate details') { console.log(` ${chalk_1.default.cyan('Certificate:')} ${info.ssl.certificate}`); } if (info.ssl.issuer) { console.log(` ${chalk_1.default.cyan('Issuer:')} ${info.ssl.issuer}`); } if (info.ssl.validFrom) { console.log(` ${chalk_1.default.cyan('Valid From:')} ${formatDate(info.ssl.validFrom)}`); } if (info.ssl.expiresAt && info.ssl.expiresAt !== 'Unable to read certificate details') { const expiryColor = info.ssl.daysUntilExpiry && info.ssl.daysUntilExpiry < 30 ? chalk_1.default.red : info.ssl.daysUntilExpiry && info.ssl.daysUntilExpiry < 60 ? chalk_1.default.yellow : chalk_1.default.green; console.log(` ${chalk_1.default.cyan('Expires:')} ${expiryColor(formatDate(info.ssl.expiresAt))}`); if (info.ssl.daysUntilExpiry !== undefined) { const daysText = info.ssl.daysUntilExpiry > 0 ? `${info.ssl.daysUntilExpiry} days remaining` : `Expired ${Math.abs(info.ssl.daysUntilExpiry)} days ago`; console.log(` ${chalk_1.default.cyan('Status:')} ${expiryColor(daysText)}`); } } } console.log(); console.log(chalk_1.default.gray('Web Interface: Visit https://forgecli.tech/deployments and enter ID:'), chalk_1.default.yellow(info.id)); console.log(); } function calculateUptime(startTime) { const start = new Date(startTime); const now = new Date(); const diff = now.getTime() - start.getTime(); const days = Math.floor(diff / (1000 * 60 * 60 * 24)); const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); if (days > 0) { return `${days}d ${hours}h ${minutes}m`; } else if (hours > 0) { return `${hours}h ${minutes}m`; } else { return `${minutes}m`; } } function formatUptime(seconds) { const days = Math.floor(seconds / (24 * 60 * 60)); const hours = Math.floor((seconds % (24 * 60 * 60)) / (60 * 60)); const minutes = Math.floor((seconds % (60 * 60)) / 60); if (days > 0) { return `${days}d ${hours}h ${minutes}m`; } else if (hours > 0) { return `${hours}h ${minutes}m`; } else { return `${minutes}m`; } } function getStatusBadge(status) { switch (status.toLowerCase()) { case 'running': case 'deployed': return chalk_1.default.green(`● ${status.toUpperCase()}`); case 'building': case 'deploying': return chalk_1.default.yellow(`● ${status.toUpperCase()}`); case 'stopped': case 'paused': return chalk_1.default.gray(`● ${status.toUpperCase()}`); case 'failed': case 'error': return chalk_1.default.red(`● ${status.toUpperCase()}`); default: return chalk_1.default.gray(`● ${status.toUpperCase()}`); } } function getHealthBadge(health) { switch (health.toLowerCase()) { case 'healthy': return chalk_1.default.green('✓ HEALTHY'); case 'unhealthy': return chalk_1.default.red('✗ UNHEALTHY'); default: return chalk_1.default.gray('? UNKNOWN'); } } function getUsageBar(percentage) { const barLength = 20; const filledLength = Math.round((percentage / 100) * barLength); const emptyLength = barLength - filledLength; let color = chalk_1.default.green; if (percentage > 70) color = chalk_1.default.yellow; if (percentage > 90) color = chalk_1.default.red; return color('█'.repeat(filledLength)) + chalk_1.default.gray('█'.repeat(emptyLength)) + ' '; } function formatDate(dateString) { return new Date(dateString).toLocaleString(); } //# sourceMappingURL=info.js.map