forge-deploy-cli
Version:
Professional CLI for local deployments with automatic subdomain routing, SSL certificates, and infrastructure management
551 lines • 24.5 kB
JavaScript
;
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-new.js.map