@aerocorp/cli
Version:
AeroCorp CLI 5.1.0 - Future-Proofed Enterprise Infrastructure with Live Preview, Tunneling & Advanced DevOps
324 lines ⢠14.4 kB
JavaScript
;
/**
* 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