UNPKG

@gati-framework/cli

Version:

CLI tool for Gati framework - create, develop, build and deploy cloud-native applications

224 lines 9.36 kB
/** * @module cli/commands/deploy * @description Deploy Gati application to cloud infrastructure */ import ora from 'ora'; import chalk from 'chalk'; import prompts from 'prompts'; import { existsSync, readFileSync } from 'fs'; import { join } from 'path'; import { generateCompleteManifests } from '../deployment/kubernetes.js'; import { executeLocalDeploy } from '../deployment/local.js'; /** * Load gati.config.ts or gati.config.js */ async function loadGatiConfig(cwd) { const configPaths = [ join(cwd, 'gati.config.ts'), join(cwd, 'gati.config.js'), join(cwd, 'package.json'), ]; for (const configPath of configPaths) { if (existsSync(configPath)) { if (configPath.endsWith('package.json')) { const pkg = JSON.parse(readFileSync(configPath, 'utf-8')); return { name: pkg.name || 'gati-app', version: pkg.version, port: 3000, }; } // For now, return default config - will implement dynamic import later return { name: 'gati-app', port: 3000, }; } } return null; } /** * Validate environment configuration */ function validateEnvironment(env) { const errors = []; if (!env.name) { errors.push('Environment name is required'); } if (!env.provider) { errors.push('Provider is required (aws, gcp, azure, or kubernetes)'); } if (env.provider === 'aws' && !env.region) { errors.push('AWS region is required for AWS deployments'); } if (!env.namespace) { errors.push('Kubernetes namespace is required'); } return errors; } /** * Deploy command handler */ async function deployCommand(environment, options) { const cwd = process.cwd(); const spinner = ora(); try { // Load configuration spinner.start('Loading configuration...'); const config = await loadGatiConfig(cwd); if (!config) { spinner.fail(chalk.red('Could not find gati.config.ts or package.json')); process.exit(1); } spinner.succeed(chalk.green(`Loaded config for: ${config.name}`)); // Select environment let targetEnv = environment || options.env || 'dev'; if (!environment && !options.env) { const response = await prompts({ type: 'select', name: 'env', message: 'Select deployment environment:', choices: [ { title: 'Development', value: 'dev' }, { title: 'Staging', value: 'staging' }, { title: 'Production', value: 'prod' }, ], }); if (!response.env) { console.log(chalk.yellow('Deployment cancelled')); process.exit(0); } targetEnv = response.env; } // Get environment config const envConfig = config.environments?.[targetEnv] || { name: targetEnv, provider: options.provider || 'kubernetes', namespace: `${config.name}-${targetEnv}`, replicas: targetEnv === 'prod' ? 3 : 1, resources: { requests: { cpu: '100m', memory: '128Mi' }, limits: { cpu: '500m', memory: '512Mi' }, }, }; // Validate environment const validationErrors = validateEnvironment(envConfig); if (validationErrors.length > 0) { spinner.fail(chalk.red('Environment configuration errors:')); validationErrors.forEach(err => console.log(chalk.red(` • ${err}`))); process.exit(1); } console.log(chalk.blue(`\n📦 Deploying to: ${chalk.bold(targetEnv)}`)); console.log(chalk.gray(` Provider: ${envConfig.provider}`)); console.log(chalk.gray(` Namespace: ${envConfig.namespace}`)); console.log(chalk.gray(` Replicas: ${envConfig.replicas}`)); if (options.dryRun) { console.log(chalk.yellow('\n🔍 DRY RUN MODE - No actual deployment will occur\n')); } // Generate manifests spinner.start('Generating Kubernetes manifests...'); const deployEnv = targetEnv === 'prod' ? 'production' : targetEnv === 'staging' ? 'staging' : 'development'; const manifests = generateCompleteManifests(config.name, envConfig.namespace || 'default', deployEnv, { nodeVersion: '20', port: config.port || 3000, replicas: envConfig.replicas || 1, image: `${config.name}:${config.version || 'latest'}`, enableAutoscaling: targetEnv === 'prod', serviceType: targetEnv === 'prod' ? 'LoadBalancer' : 'ClusterIP', }); spinner.succeed(chalk.green('Manifests generated successfully')); if (options.dryRun) { console.log(chalk.cyan('\n📄 Generated Manifests:\n')); console.log(chalk.gray('--- Dockerfile ---')); console.log(manifests.dockerfile); console.log(chalk.gray('\n--- Deployment ---')); console.log(manifests.deployment); console.log(chalk.gray('\n--- Service ---')); console.log(manifests.service); console.log(chalk.green('\n✅ Dry run completed successfully')); return; } // Local deployment flow (kind) if requested or provider == kubernetes if (options.local || envConfig.provider === 'kubernetes') { spinner.stop(); await executeLocalDeploy({ appName: config.name, namespace: envConfig.namespace || `${config.name}-${targetEnv}`, env: deployEnv, workingDir: cwd, clusterName: options.clusterName, skipCluster: options.skipCluster, dryRun: options.dryRun, healthCheckPath: options.healthCheckPath, portForward: options.portForward, timeoutSeconds: options.timeout ? parseInt(options.timeout, 10) : undefined, autoTag: options.autoTag, replicas: envConfig.replicas, verbose: options.verbose, imageTag: `${config.name}:${config.version || 'latest'}`, port: config.port || 3000, }); } else { // Build Docker image (if not skipped) if (!options.skipBuild) { spinner.start('Building Docker image...'); spinner.info(chalk.yellow('Docker build not yet implemented')); } spinner.start(`Deploying to ${envConfig.provider}...`); switch (envConfig.provider) { case 'aws': spinner.info(chalk.yellow('AWS EKS deployment not yet implemented')); break; case 'gcp': spinner.info(chalk.yellow('GCP GKE deployment not yet implemented')); break; case 'azure': spinner.info(chalk.yellow('Azure AKS deployment not yet implemented')); break; default: spinner.fail(chalk.red(`Unknown provider: ${envConfig.provider}`)); process.exit(1); } console.log(chalk.green('\n✨ Deployment prepared successfully!')); console.log(chalk.gray('\nNext steps:')); console.log(chalk.gray(' 1. Review generated manifests')); console.log(chalk.gray(' 2. Configure cloud provider credentials')); console.log(chalk.gray(' 3. Run deploy without --dry-run')); } } catch (error) { spinner.fail(chalk.red('Deployment failed')); console.error(chalk.red('\n' + error.message)); if (process.env['DEBUG']) { console.error(error); } process.exit(1); } } /** * Register deploy command */ export function registerDeployCommand(program) { program .command('deploy [environment]') .description('Deploy application to cloud infrastructure') .option('-e, --env <environment>', 'Target environment (dev, staging, prod)') .option('--dry-run', 'Generate manifests without deploying') .option('--skip-build', 'Skip Docker image build') .option('-p, --provider <provider>', 'Cloud provider (aws, gcp, azure, kubernetes)') .option('--local', 'Force local (kind) deployment flow') .option('--health-check-path <path>', 'Probe HTTP path after rollout (e.g., /health)') .option('--port-forward', 'Port-forward service to local port with auto cleanup (non-test mode holds until Ctrl+C)') .option('--timeout <seconds>', 'Rollout timeout in seconds (default: 120)') .option('--auto-tag', 'Auto-tag image with git SHA + timestamp') .option('--cluster-name <name>', 'Kind cluster name (default: gati-local)') .option('--skip-cluster', 'Skip cluster creation step') .option('-v, --verbose', 'Verbose output') .action(deployCommand); } //# sourceMappingURL=deploy.js.map