UNPKG

@aerocorp/cli

Version:

AeroCorp CLI 5.1.0 - Future-Proofed Enterprise Infrastructure with Live Preview, Tunneling & Advanced DevOps

324 lines • 14.4 kB
"use strict"; /** * AeroCorp CLI 5.0.0 - Preview Service * Live preview functionality with tunneling support */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PreviewService = void 0; const child_process_1 = require("child_process"); const chokidar_1 = __importDefault(require("chokidar")); const localtunnel_1 = __importDefault(require("localtunnel")); const ngrok_1 = __importDefault(require("ngrok")); const open_1 = __importDefault(require("open")); const qrcode_terminal_1 = __importDefault(require("qrcode-terminal")); const chalk_1 = __importDefault(require("chalk")); const fs_extra_1 = __importDefault(require("fs-extra")); const config_1 = require("./config"); class PreviewService { constructor() { this.configService = new config_1.ConfigService(); } async startPreview(options = {}) { const port = options.port || 3000; const localUrl = `http://localhost:${port}`; console.log(chalk_1.default.cyan.bold('\nšŸš€ Starting AeroCorp Preview Server')); console.log(chalk_1.default.gray(`Local URL: ${localUrl}`)); // Check if it's a Vite project const isViteProject = await fs_extra_1.default.pathExists('vite.config.ts') || await fs_extra_1.default.pathExists('vite.config.js'); const isNextProject = await fs_extra_1.default.pathExists('next.config.js') || await fs_extra_1.default.pathExists('next.config.ts'); const isReactProject = await fs_extra_1.default.pathExists('package.json') && (await fs_extra_1.default.readJson('package.json')).dependencies?.react; let devCommand; let devArgs = []; if (isViteProject) { devCommand = 'npm'; devArgs = ['run', 'dev', '--', '--port', port.toString(), '--host', '0.0.0.0']; console.log(chalk_1.default.blue('šŸ“¦ Detected Vite project')); } else if (isNextProject) { devCommand = 'npm'; devArgs = ['run', 'dev', '--', '--port', port.toString()]; console.log(chalk_1.default.blue('šŸ“¦ Detected Next.js project')); } else if (isReactProject) { devCommand = 'npm'; devArgs = ['start']; console.log(chalk_1.default.blue('šŸ“¦ Detected React project')); } else { // Fallback to Express server devCommand = 'node'; devArgs = ['server.js']; console.log(chalk_1.default.blue('šŸ“¦ Starting Express server')); } // Start development server this.devServer = (0, child_process_1.spawn)(devCommand, devArgs, { stdio: ['pipe', 'pipe', 'pipe'], shell: true, env: { ...process.env, PORT: port.toString() } }); // Handle server output this.devServer.stdout?.on('data', (data) => { const output = data.toString(); if (output.includes('Local:') || output.includes('ready') || output.includes('started')) { console.log(chalk_1.default.green('āœ… Development server started')); } }); this.devServer.stderr?.on('data', (data) => { const error = data.toString(); if (!error.includes('Warning') && !error.includes('deprecated')) { console.log(chalk_1.default.yellow('āš ļø '), error.trim()); } }); // Wait for server to start await this.waitForServer(port); const session = { localUrl, port, pid: this.devServer.pid, startTime: new Date() }; // Setup tunneling if requested if (options.tunnel && options.tunnel !== 'none') { session.publicUrl = await this.setupTunnel(port, options.tunnel, options.subdomain); session.tunnel = options.tunnel; } // Setup file watching for live reload if (options.watch !== false) { await this.setupFileWatcher(); } // Open browser if requested if (options.open) { const urlToOpen = session.publicUrl || session.localUrl; await (0, open_1.default)(urlToOpen); console.log(chalk_1.default.green(`🌐 Opened ${urlToOpen} in browser`)); } // Show QR code if requested if (options.qr && session.publicUrl) { console.log(chalk_1.default.cyan('\nšŸ“± QR Code for mobile access:')); qrcode_terminal_1.default.generate(session.publicUrl, { small: true }); } this.displayPreviewInfo(session); return session; } async setupTunnel(port, tunnelType, subdomain) { console.log(chalk_1.default.blue(`šŸ”— Setting up ${tunnelType} tunnel...`)); try { switch (tunnelType) { case 'localtunnel': this.tunnel = await (0, localtunnel_1.default)({ port, subdomain: subdomain || `aerocorp-${Date.now()}` }); console.log(chalk_1.default.green(`āœ… LocalTunnel: ${this.tunnel.url}`)); return this.tunnel.url; case 'ngrok': const ngrokUrl = await ngrok_1.default.connect({ port, subdomain, region: 'us' }); console.log(chalk_1.default.green(`āœ… Ngrok: ${ngrokUrl}`)); return ngrokUrl; case 'cloudflare': // For Cloudflare Tunnel, we'd need cloudflared binary console.log(chalk_1.default.yellow('āš ļø Cloudflare Tunnel requires cloudflared binary')); console.log(chalk_1.default.blue('šŸ’” Install: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/')); return `http://localhost:${port}`; default: throw new Error(`Unsupported tunnel type: ${tunnelType}`); } } catch (error) { console.log(chalk_1.default.red(`āŒ Tunnel setup failed: ${error.message}`)); console.log(chalk_1.default.yellow(`šŸ”„ Falling back to local preview only`)); return `http://localhost:${port}`; } } async setupFileWatcher() { const watchPaths = ['src', 'public', 'components', 'pages', 'styles']; const existingPaths = []; for (const watchPath of watchPaths) { if (await fs_extra_1.default.pathExists(watchPath)) { existingPaths.push(watchPath); } } if (existingPaths.length === 0) { existingPaths.push('.'); } this.watcher = chokidar_1.default.watch(existingPaths, { ignored: /node_modules|\.git|dist|build/, persistent: true }); this.watcher.on('change', (filePath) => { console.log(chalk_1.default.blue(`šŸ”„ File changed: ${filePath}`)); }); console.log(chalk_1.default.green(`šŸ‘€ Watching ${existingPaths.join(', ')} for changes`)); } async waitForServer(port, timeout = 30000) { const startTime = Date.now(); while (Date.now() - startTime < timeout) { try { const response = await fetch(`http://localhost:${port}`); if (response.ok || response.status === 404) { return; } } catch (error) { // Server not ready yet } await new Promise(resolve => setTimeout(resolve, 1000)); } throw new Error('Development server failed to start within timeout'); } displayPreviewInfo(session) { console.log(chalk_1.default.cyan.bold('\nšŸŽÆ Preview Session Active')); console.log(chalk_1.default.white('─'.repeat(50))); console.log(chalk_1.default.white(`šŸ“ Local: ${session.localUrl}`)); if (session.publicUrl) { console.log(chalk_1.default.white(`🌐 Public: ${session.publicUrl}`)); console.log(chalk_1.default.white(`šŸ”— Tunnel: ${session.tunnel}`)); } console.log(chalk_1.default.white(`šŸ†” PID: ${session.pid}`)); console.log(chalk_1.default.white(`ā° Started: ${session.startTime.toLocaleTimeString()}`)); console.log(chalk_1.default.white('─'.repeat(50))); console.log(chalk_1.default.gray('Press Ctrl+C to stop the preview server')); } async stopPreview() { console.log(chalk_1.default.blue('\nšŸ›‘ Stopping preview server...')); // Stop file watcher if (this.watcher) { await this.watcher.close(); console.log(chalk_1.default.green('āœ… File watcher stopped')); } // Close tunnel if (this.tunnel) { if (typeof this.tunnel.close === 'function') { this.tunnel.close(); } else { await ngrok_1.default.disconnect(); } console.log(chalk_1.default.green('āœ… Tunnel closed')); } // Stop development server if (this.devServer) { this.devServer.kill('SIGTERM'); console.log(chalk_1.default.green('āœ… Development server stopped')); } console.log(chalk_1.default.cyan('šŸ‘‹ Preview session ended')); } async listActiveSessions() { // In a real implementation, this would track active sessions // For now, return empty array return []; } async deployPreview(options = {}) { try { console.log(chalk_1.default.blue('šŸš€ Creating Coolify preview deployment...')); // Import CoolifyService const { CoolifyService } = await Promise.resolve().then(() => __importStar(require('./coolify'))); const coolifyService = new CoolifyService(); // Validate Coolify connection first const isHealthy = await coolifyService.healthCheck(); if (!isHealthy) { throw new Error('Coolify health check failed. Cannot create preview.'); } // Build the project console.log(chalk_1.default.blue('šŸ“¦ Building project...')); const buildProcess = (0, child_process_1.spawn)('npm', ['run', 'build'], { stdio: 'inherit', shell: true }); await new Promise((resolve, reject) => { buildProcess.on('close', (code) => { if (code === 0) { resolve(void 0); } else { reject(new Error(`Build failed with code ${code}`)); } }); }); // Create preview deployment via Coolify if (options.pr && options.app && options.branch) { const previewUrl = await coolifyService.createPreview({ pr: options.pr, app: options.app, branch: options.branch }); console.log(chalk_1.default.green('āœ… Coolify preview deployment created!')); console.log(chalk_1.default.white(`🌐 Preview URL: ${previewUrl}`)); return previewUrl; } else { // Fallback to regular deployment const deployResult = await coolifyService.deploy({ app: options.app || options.name, branch: options.branch, environment: 'preview' }); const previewUrl = deployResult.url || `https://preview-${Date.now()}.aerocorpindustries.org`; console.log(chalk_1.default.green('āœ… Preview deployment created!')); console.log(chalk_1.default.white(`🌐 Preview URL: ${previewUrl}`)); return previewUrl; } } catch (error) { throw new Error(`Preview deployment failed: ${error.message}`); } } /** * Destroy preview deployment */ async destroyPreview(prNumber, appUuid) { try { console.log(chalk_1.default.blue(`šŸ—‘ļø Destroying preview for PR #${prNumber}...`)); const { CoolifyService } = await Promise.resolve().then(() => __importStar(require('./coolify'))); const coolifyService = new CoolifyService(); await coolifyService.destroyPreview(prNumber, appUuid); console.log(chalk_1.default.green('āœ… Preview environment destroyed')); } catch (error) { throw new Error(`Preview destruction failed: ${error.message}`); } } } exports.PreviewService = PreviewService; //# sourceMappingURL=preview.js.map