UNPKG

cfn-forge

Version:

CloudFormation deployment automation tool with git-based workflows

187 lines (151 loc) • 5.47 kB
/** * Watch Command - Cross-platform deployment watcher using Node.js */ const path = require('path'); const fs = require('fs').promises; const yaml = require('js-yaml'); const { logger } = require('../utils/logger'); const DeploymentEngine = require('../../lib/core/deployment/DeploymentEngine'); async function watchCommand(options) { const command = this; logger.info('Starting deployment watcher...'); try { // Check if in a cfn-forge project try { await fs.access('.cfn-forge.yaml'); } catch { logger.error('Not in a cfn-forge project!'); logger.info('Run "cfn-forge init" to create a new project'); process.exit(1); } // Load project configuration const configContent = await fs.readFile('.cfn-forge.yaml', 'utf8'); const config = yaml.load(configContent); // Determine environment const environment = command.parent.opts().env || config.defaultEnvironment || 'development'; if (!config.environments || !config.environments[environment]) { logger.error(`Environment '${environment}' not found in configuration`); logger.info(`Available environments: ${Object.keys(config.environments || {}).join(', ')}`); process.exit(1); } const envConfig = config.environments[environment]; logger.info(`Using environment: ${environment}`); logger.info(`Stack: ${envConfig.stackName}`); logger.info(`Region: ${envConfig.region || process.env.AWS_REGION || 'default'}`); // Create deployment engine with AWS config const deploymentEngine = new DeploymentEngine({ aws: { region: envConfig.region || command.parent.opts().region || process.env.AWS_REGION, profile: command.parent.opts().profile || envConfig.profile, }, deployOnTags: config.deployOnTags || false, watchPaths: ['.'], ignorePatterns: [ 'node_modules/**', '.git/**', 'dist/**', 'build/**', '**/*.tmp', '.cfn-forge/**', 'deploy/**', ...(options.ignore ? [options.ignore] : []), ], }); // Initialize engine await deploymentEngine.initialize({ environment, ...config, }); // Set up event listeners deploymentEngine.on('watchingStarted', ({ paths }) => { logger.info(`šŸ‘€ Watching paths: ${paths.join(', ')}`); logger.info('šŸ“ Waiting for git changes...'); logger.info('Press Ctrl+C to stop'); }); deploymentEngine.on('deploymentStarted', ({ stackName }) => { logger.info(`\nšŸš€ Deploying stack: ${stackName}`); }); deploymentEngine.on('waitingForStack', ({ stackName, operation }) => { logger.info(`ā³ Waiting for stack ${operation}...`); }); deploymentEngine.on('deploymentCompleted', ({ status, operation, stackName }) => { if (status === 'SUCCESS') { logger.success(`āœ… Deployment ${operation} completed successfully!`); } else if (status === 'SKIPPED') { logger.info('ā­ļø No changes detected - skipping deployment'); } }); deploymentEngine.on('deploymentFailed', ({ error }) => { logger.error(`āŒ Deployment failed: ${error}`); }); // Prepare deployment options const deployOptions = { environment, stackName: envConfig.stackName, templatePath: envConfig.template || config.template || 'template.yaml', parameters: prepareParameters(envConfig, options), tags: { ...config.tags, ...envConfig.tags, Environment: environment, }, force: false, }; // Start watching await deploymentEngine.startWatching({ interval: options.interval ? parseInt(options.interval) : 2000, deploymentOptions: deployOptions, }); // Handle graceful shutdown const shutdown = async () => { logger.info('\nšŸ›‘ Shutting down deployment watcher...'); await deploymentEngine.stopWatching(); process.exit(0); }; process.on('SIGINT', shutdown); process.on('SIGTERM', shutdown); // Keep process alive process.stdin.resume(); } catch (error) { logger.error(`Failed to start deployment watcher: ${error.message}`); process.exit(1); } } /** * Prepare parameters for deployment */ function prepareParameters(envConfig, options) { let allParameters = {}; // Start with environment-specific parameters from .cfn-forge.yaml if (envConfig.parameters) { allParameters = { ...envConfig.parameters }; } // Parse CLI parameters if provided (highest priority) if (options.parameters) { const cliParams = parseParameterString(options.parameters); Object.assign(allParameters, cliParams); } return allParameters; } /** * Parse parameter string from CLI (key=value,key2=value2) */ function parseParameterString(paramString) { const parameters = {}; // Split by comma and parse key=value pairs const pairs = paramString.split(',').map((pair) => pair.trim()).filter((pair) => pair); for (const pair of pairs) { const equalIndex = pair.indexOf('='); if (equalIndex === -1) { throw new Error(`Invalid parameter format: "${pair}" (expected key=value)`); } const key = pair.substring(0, equalIndex).trim(); const value = pair.substring(equalIndex + 1).trim(); if (!key) { throw new Error(`Empty parameter key in: "${pair}"`); } parameters[key] = value; } return parameters; } module.exports = watchCommand;