UNPKG

cfn-forge

Version:

CloudFormation deployment automation tool with git-based workflows

233 lines (194 loc) 6.73 kB
/** * Deployment utilities for managing bash script installation */ const fs = require('fs').promises; const path = require('path'); const { logger } = require('./logger'); async function ensureDeploymentScripts() { const deployDir = path.join(process.cwd(), 'deploy'); const modulesDir = path.join(deployDir, 'modules'); try { // Create directories await fs.mkdir(modulesDir, { recursive: true }); // Copy main deploy script const mainScriptSource = path.join(process.env.CFN_FORGE_CORE, 'deploy.sh'); const mainScriptDest = path.join(deployDir, 'deploy.sh'); await copyDeployScript(mainScriptSource, mainScriptDest); // Copy all modules const modules = [ 'constants.sh', 'utils.sh', 'error-handling.sh', 'git-utils.sh', 'aws-utils.sh', 'validation.sh', 'interactive.sh', 'deployment.sh', ]; for (const module of modules) { const source = path.join(process.env.CFN_FORGE_CORE, 'modules', module); const dest = path.join(modulesDir, module); await copyDeployScript(source, dest); } // Create initial .env file if it doesn't exist const envFile = path.join(deployDir, '.env'); const envExists = await fs.access(envFile).then(() => true).catch(() => false); if (!envExists) { await createInitialEnvFile(envFile); } // Remove install marker try { await fs.unlink(path.join(deployDir, '.install-required')); } catch { // Ignore if doesn't exist } logger.success('Deployment scripts installed successfully'); } catch (error) { logger.error(`Failed to install deployment scripts: ${error.message}`); throw error; } } async function copyDeployScript(source, dest) { try { const content = await fs.readFile(source, 'utf8'); // Update any paths that need to be relative to the project const updatedContent = content .replace(/\$CFN_FORGE_HOME/g, process.env.CFN_FORGE_HOME) .replace(/\$CFN_FORGE_ROOT/g, process.env.CFN_FORGE_ROOT); await fs.writeFile(dest, updatedContent); await fs.chmod(dest, 0o755); // Make executable } catch (error) { // If source doesn't exist, try to download from GitHub if (error.code === 'ENOENT') { await downloadDeployScript(source, dest); } else { throw error; } } } async function downloadDeployScript(source, dest) { const scriptName = path.basename(source); // Allow branch override via environment variable, default to main for stability const branch = process.env.CFN_FORGE_BRANCH || 'main'; const baseUrl = `https://raw.githubusercontent.com/dmleblanc/cfn-forge/${branch}/src/core`; let url; if (scriptName === 'deploy.sh') { url = `${baseUrl}/deploy.sh`; } else { url = `${baseUrl}/modules/${scriptName}`; } logger.debug(`Downloading ${scriptName} from ${url} (branch: ${branch})`); const https = require('https'); const content = await new Promise((resolve, reject) => { https.get(url, (res) => { let data = ''; res.on('data', (chunk) => data += chunk); res.on('end', () => resolve(data)); res.on('error', reject); }).on('error', reject); }); await fs.writeFile(dest, content); await fs.chmod(dest, 0o755); } async function createInitialEnvFile(envFile) { const projectConfig = await loadProjectConfig(); const environment = process.env.CFN_FORGE_ENV || 'dev'; const envConfig = projectConfig.environments?.[environment] || {}; const content = `# Auto-generated by cfn-forge # Edit this file to customize your deployment configuration # CloudFormation Stack Configuration STACK_NAME=${envConfig.stackName || projectConfig.project?.name || 'my-stack'} TEMPLATE_FILE=../infrastructure/main.yaml AWS_REGION=${envConfig.region || projectConfig.defaults?.region || 'us-east-1'} SECRET_NAME=${envConfig.secretName || `${envConfig.stackName}-secrets`} # S3 Deployment Configuration BUCKET_NAME=${envConfig.bucketName || `${envConfig.stackName}-deployments-${Date.now()}`} # Lambda Configuration LAMBDA_SOURCE_DIR=${projectConfig.lambda?.sourceDir || '../lambda'} # Deployment Behavior DEPLOY_ON_TAGS=${envConfig.deployOnTags === true ? 'true' : 'false'} # Environment ENVIRONMENT=${environment} # Auto-generated values (managed by deployment script) # LAMBDA_S3_KEY= # VERSION= # GIT_COMMIT= # GIT_BRANCH= # GIT_TAG= # LAST_DEPLOY_DATE= # LAST_SUCCESSFUL_DEPLOY_DATE= # LAST_DEPLOY_FAILED= `; await fs.writeFile(envFile, content); logger.debug('Created initial .env file'); } async function loadProjectConfig() { try { const yaml = require('js-yaml'); const configPath = path.join(process.cwd(), '.cfn-forge.yaml'); const content = await fs.readFile(configPath, 'utf8'); return yaml.load(content); } catch { return {}; } } async function updateDeploymentScripts() { logger.info('Checking for deployment script updates...'); const deployDir = path.join(process.cwd(), 'deploy'); const versionFile = path.join(deployDir, '.version'); try { const currentVersion = await fs.readFile(versionFile, 'utf8').catch(() => '0.0.0'); const latestVersion = require('../../../package.json').version; if (currentVersion !== latestVersion) { logger.info(`Updating deployment scripts from ${currentVersion} to ${latestVersion}`); await ensureDeploymentScripts(); await fs.writeFile(versionFile, latestVersion); logger.success('Deployment scripts updated successfully'); } else { logger.debug('Deployment scripts are up to date'); } } catch (error) { logger.error(`Failed to update deployment scripts: ${error.message}`); } } async function validateDeploymentEnvironment() { const checks = [ { name: 'AWS CLI', command: 'aws --version', error: 'AWS CLI is not installed. Please install it first.', }, { name: 'Git', command: 'git --version', error: 'Git is not installed. Please install it first.', }, { name: 'fswatch', command: 'fswatch --version', error: 'fswatch is not installed. Please install it first.', optional: true, }, ]; const results = []; for (const check of checks) { try { const { execSync } = require('child_process'); execSync(check.command, { stdio: 'ignore' }); results.push({ name: check.name, status: 'ok' }); } catch { results.push({ name: check.name, status: check.optional ? 'warning' : 'error', message: check.error, }); } } return results; } module.exports = { ensureDeploymentScripts, updateDeploymentScripts, validateDeploymentEnvironment, loadProjectConfig, };