UNPKG

@owloops/cdko

Version:

Multi-region AWS CDK deployment tool

983 lines (975 loc) 33.4 kB
// src/core/stack-manager.ts import { $ } from "zx"; import fs from "fs/promises"; // src/utils/pattern-matcher.ts function matchPattern(text, pattern) { if (pattern === "*") { return true; } if (!pattern.includes("*")) { return text === pattern; } if (pattern.startsWith("*") && pattern.endsWith("*")) { const middle = pattern.slice(1, -1); return text.includes(middle); } if (pattern.startsWith("*")) { const suffix = pattern.slice(1); return text.endsWith(suffix); } if (pattern.endsWith("*")) { const prefix = pattern.slice(0, -1); return text.startsWith(prefix); } const parts = pattern.split("*"); if (parts.length === 2) { const [prefix, suffix] = parts; return text.startsWith(prefix || "") && text.endsWith(suffix || ""); } return text === pattern; } // src/utils/logger.ts import { chalk } from "zx"; var logger = { error: (msg) => console.error(chalk.red("\u2717"), msg), warn: (msg) => console.log(chalk.yellow("\u26A0"), msg), info: (msg) => console.log(chalk.blue("\u2022"), msg), success: (msg) => console.log(chalk.green("\u2713"), msg), header: (msg) => console.log(chalk.bold.blue(msg)), subheader: (msg) => console.log(chalk.dim(msg)), phase: (phase, msg) => console.log(chalk.bold(`${phase}:`), msg), dim: (text) => chalk.dim(text) }; // src/core/stack-manager.ts var StackManager = class { configPath; constructor(configPath = ".cdko.json") { this.configPath = configPath; } async detect() { try { await fs.access("cdk.json"); } catch { throw new Error( "No CDK project found. Run this command in a CDK project directory." ); } const configExists = await fs.access(this.configPath).then(() => true).catch(() => false); if (configExists) { logger.info("Updating existing CDKO configuration..."); } else { logger.info("Creating new CDKO configuration..."); } const config = await this.detectStacks(); await this.saveConfig(config); const action = configExists ? "updated" : "created"; logger.success(`CDKO configuration ${action} successfully!`); logger.info(`Found ${Object.keys(config.stackGroups).length} stack groups`); return config; } async loadConfig() { try { const data = await fs.readFile(this.configPath, "utf8"); const config = JSON.parse(data); if (config.stackGroups && typeof config.stackGroups !== "object") { logger.warn("Config: stackGroups must be an object, ignoring"); config.stackGroups = void 0; } return config; } catch (error) { if (error instanceof Error && "code" in error && error.code !== "ENOENT") { logger.warn(`Failed to load ${this.configPath}: ${error.message}`); } } return {}; } async detectStacks() { logger.info("Detecting CDK stacks..."); try { const result = await $`cdk list --long --json`.quiet(); const stacks = JSON.parse(result.stdout); if (!Array.isArray(stacks) || stacks.length === 0) { throw new Error( "No stacks found. Ensure your CDK app synthesizes correctly." ); } const stackGroups = {}; for (const stack of stacks) { const stackName = stack.name || stack.id; const account = stack.environment?.account; const region = stack.environment?.region; let constructId = stack.id; const parenIndex = constructId.indexOf(" ("); if (parenIndex > -1) { constructId = constructId.substring(0, parenIndex); } if (!stackGroups[stackName]) { stackGroups[stackName] = {}; } const deploymentKey = `${account}/${region}`; stackGroups[stackName][deploymentKey] = { constructId, account, region }; } return { version: "0.1", stackGroups, cdkTimeout: process.env.CDK_TIMEOUT, suppressNotices: process.env.CDK_CLI_NOTICES !== "true", lastUpdated: (/* @__PURE__ */ new Date()).toISOString(), updatedBy: `cdko@${await this.getCdkoVersion()}` }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes("No stacks found")) { throw error; } logger.error(`Stack detection failed: ${errorMessage}`); throw new Error( "Failed to detect stacks. Ensure CDK app synthesizes correctly." ); } } async getCdkoVersion() { try { const packagePath = new URL("../../package.json", import.meta.url); const data = await fs.readFile(packagePath, "utf8"); const packageJson = JSON.parse(data); return packageJson.version || "0.0.0"; } catch { return "0.0.0"; } } async saveConfig(config) { await fs.writeFile(this.configPath, JSON.stringify(config, null, 2) + "\n"); logger.info(`Configuration saved to ${this.configPath}`); } getRegions(config, cliRegions) { if (cliRegions === "all") { if (config?.stackGroups) { const allRegions = Object.values(config.stackGroups).flatMap( (group) => Object.values(group).map((deployment) => deployment.region) ); const uniqueRegions = [...new Set(allRegions)]; if (uniqueRegions.length > 0) { return uniqueRegions; } } return ["us-east-1"]; } return cliRegions.split(",").map((r) => r.trim()); } matchStacks(stackPattern, stackConfig) { if (!stackConfig?.stackGroups) { return []; } const patterns = stackPattern.split(",").map((p) => p.trim()); const matchedGroups = []; for (const pattern of patterns) { for (const [stackGroupName, deployments] of Object.entries( stackConfig.stackGroups )) { if (matchPattern(stackGroupName, pattern)) { matchedGroups.push({ name: stackGroupName, pattern, deployments }); } } } return matchedGroups; } filterDeployments(stackGroup, requestedRegions) { const filteredDeployments = []; for (const [, deployment] of Object.entries(stackGroup.deployments)) { const { region, constructId } = deployment; if (region === "unknown-region") { for (const requestedRegion of requestedRegions) { filteredDeployments.push({ region: requestedRegion, constructId, stackName: stackGroup.name }); } } else { if (requestedRegions.includes(region)) { filteredDeployments.push({ region, constructId, stackName: stackGroup.name }); } } } return filteredDeployments; } resolveDeployments(stackPattern, stackConfig, requestedRegions) { const matchedGroups = this.matchStacks(stackPattern, stackConfig); if (matchedGroups.length === 0) { logger.warn(`No stacks found matching pattern: ${stackPattern}`); return []; } logger.info(`Found ${matchedGroups.length} stack(s) matching pattern`); const allDeployments = []; for (const stackGroup of matchedGroups) { const deployments = this.filterDeployments(stackGroup, requestedRegions); if (deployments.length === 0) { logger.warn( `No valid deployments for ${stackGroup.name} in requested regions` ); } else { logger.info( `${stackGroup.name}: ${deployments.length} deployment(s) planned` ); allDeployments.push(...deployments); } } return allDeployments; } }; // src/core/account-manager.ts import { $ as $2 } from "zx"; var AccountManager = class { accountCache; constructor() { this.accountCache = /* @__PURE__ */ new Map(); } async getAvailableProfiles() { try { const result = await $2`aws configure list-profiles`.quiet(); return result.stdout.split("\n").map((profile) => profile.trim()).filter((profile) => profile.length > 0); } catch { logger.warn("Could not list AWS profiles, using exact matching only"); return []; } } async matchProfiles(profilePattern) { const patterns = profilePattern.split(",").map((p) => p.trim()); const availableProfiles = await this.getAvailableProfiles(); const matchedProfiles = []; for (const pattern of patterns) { if (availableProfiles.length > 0) { for (const profile of availableProfiles) { if (matchPattern(profile, pattern)) { if (!matchedProfiles.includes(profile)) { matchedProfiles.push(profile); } } } } else { if (!matchedProfiles.includes(pattern)) { matchedProfiles.push(pattern); } } } if (matchedProfiles.length === 0) { logger.warn(`No profiles found matching pattern: ${profilePattern}`); return patterns; } if (matchedProfiles.length > 1) { logger.info( `Found ${matchedProfiles.length} profile(s) matching pattern: ${matchedProfiles.join(", ")}` ); } return matchedProfiles; } async getAccountInfo(profile) { if (this.accountCache.has(profile)) { return this.accountCache.get(profile); } try { const result = await $2`aws sts get-caller-identity --profile ${profile} --output json`.quiet(); const identity = JSON.parse(result.stdout); const accountInfo = { profile, accountId: identity.Account, userId: identity.UserId, arn: identity.Arn }; this.accountCache.set(profile, accountInfo); return accountInfo; } catch (error) { const errorMsg = error instanceof Error && "stderr" in error ? error.stderr : error instanceof Error ? error.message : String(error); logger.error( `Failed to get account info for profile ${profile}: ${errorMsg}` ); throw new Error( `Profile '${profile}' authentication failed: ${errorMsg}` ); } } async getMultiAccountInfo(profiles) { const accountPromises = profiles.map( (profile) => this.getAccountInfo(profile).catch( (error) => ({ profile, error: error instanceof Error ? error.message : String(error), failed: true }) ) ); const results = await Promise.all(accountPromises); const successful = results.filter( (result) => !("failed" in result) ); const failed = results.filter( (result) => "failed" in result ); if (failed.length > 0) { logger.error(`Failed to authenticate ${failed.length} profile(s):`); failed.forEach(({ profile, error }) => { logger.error(` ${profile}: ${error}`); }); if (successful.length === 0) { throw new Error("All profiles failed authentication"); } logger.warn(`Continuing with ${successful.length} successful profile(s)`); } return successful; } createDeploymentTargets(accountInfo, regions) { const targets = []; accountInfo.forEach(({ profile, accountId }) => { regions.forEach((region) => { targets.push({ profile, accountId, region, key: `${accountId}/${region}` }); }); }); return targets; } clearCache() { this.accountCache.clear(); } }; // src/core/cloud-assembly.ts import { $ as $3 } from "zx"; import { existsSync, mkdirSync, rmSync } from "fs"; import { join } from "path"; var CloudAssemblyManager = class { cloudAssemblyPath; constructor() { this.cloudAssemblyPath = null; } getCloudAssemblyPath() { return join(process.cwd(), "cdk.out"); } async synthesize(options = {}) { const { profile, environment, outputDir, stacks, exclusively = true } = options; const cloudAssemblyPath = outputDir ? join(process.cwd(), outputDir) : this.getCloudAssemblyPath(); try { if (existsSync(cloudAssemblyPath)) { rmSync(cloudAssemblyPath, { recursive: true, force: true }); } mkdirSync(cloudAssemblyPath, { recursive: true }); const cdkArgs = ["synth"]; if (stacks && stacks.length > 0) { cdkArgs.push(...stacks); } if (exclusively && stacks && stacks.length > 0) { cdkArgs.push("--exclusively"); } if (process.env.CDK_CLI_NOTICES !== "true") { cdkArgs.push("--no-notices"); } cdkArgs.push("--output", cloudAssemblyPath); if (profile) { cdkArgs.push("--profile", profile); } if (environment) { cdkArgs.push("--context", `environment=${environment}`); } const result = await $3({ quiet: true })`cdk ${cdkArgs}`; if (result.exitCode === 0) { this.cloudAssemblyPath = cloudAssemblyPath; return cloudAssemblyPath; } else { throw new Error(`CDK synth failed with exit code ${result.exitCode}`); } } catch (error) { logger.error("Failed to synthesize cloud assembly"); if (error instanceof Error && "stderr" in error) { console.error(error.stderr); } throw error; } } getCdkArgs(baseArgs) { if (!this.cloudAssemblyPath || !existsSync(this.cloudAssemblyPath)) { throw new Error("Cloud assembly not available. Run synthesize() first."); } const args = [...baseArgs]; const appIndex = args.indexOf("--app"); if (appIndex !== -1) { args.splice(appIndex, 2); } args.push("--app", this.cloudAssemblyPath); return args; } isAvailable() { return !!this.cloudAssemblyPath && existsSync(this.cloudAssemblyPath); } }; // src/core/executor.ts import { $ as $4 } from "zx"; function filterOutput(output, command) { const lines = output.split("\n"); const filteredLines = []; let inStackSection = false; for (const line of lines) { const lowerLine = line.toLowerCase(); if (line.startsWith("Stack ")) { inStackSection = true; } const shouldInclude = inStackSection || line.includes("Resources") || line.includes("[~]") || line.includes("[-]") || line.includes("[+]") || lowerLine.includes("number of stacks") || line.trim().startsWith("\u2728"); if (command === "deploy") { const isProgress = line.includes(" | ") && (line.includes("CREATE_") || line.includes("UPDATE_") || line.includes("DELETE_") || line.includes("ROLLBACK_") || line.includes("REVIEW_IN_PROGRESS")) || line.includes(": deploying...") || line.includes(": creating CloudFormation changeset...") || line.includes("\u2705") || lowerLine.includes("synthesis time") || lowerLine.includes("deployment time") || lowerLine.includes("total time"); if (shouldInclude || isProgress) { filteredLines.push(line); } } else if (shouldInclude) { filteredLines.push(line); } } return filteredLines.join("\n").trim(); } function extractStackArn(output) { const cleanOutput = output.replace( new RegExp(String.fromCharCode(27) + "\\[[0-9;]*m", "g"), "" ); const stackArnMatch = cleanOutput.match( /Stack ARN:\s*\n?\s*(arn:aws:cloudformation:[^\s]*)/ ); return stackArnMatch?.[1]; } function createRegionPrefix(deploymentProfile, region) { return deploymentProfile ? `${deploymentProfile}/${region}` : region; } function prefixDiffLines(lines, regionPrefix) { return lines.map((line) => { if (line.startsWith("Stack ") || line.trim().startsWith("\u2728")) { return `[${regionPrefix}] ${line}`; } return line; }).join("\n"); } function addRegionPrefixToOutput(data, regionPrefix) { const lines = data.toString().split("\n"); lines.forEach((line) => { if (line.trim()) { const lowerLine = line.toLowerCase(); if (line.includes(" | ") && (line.includes("CREATE_") || line.includes("UPDATE_") || line.includes("DELETE_") || line.includes("ROLLBACK_") || line.includes("REVIEW_IN_PROGRESS"))) { console.log(`[${regionPrefix}] ${line}`); return; } if (line.includes(": deploying...") || line.includes(": creating CloudFormation changeset...") || line.includes("\u2705")) { console.log(`[${regionPrefix}] ${line}`); return; } if (lowerLine.includes("deployment time") || lowerLine.includes("total time")) { console.log(`[${regionPrefix}] ${line}`); return; } if (lowerLine.includes("synthesis time")) { return; } if (line.trim().startsWith("\u2728") && !lowerLine.includes("synthesis time")) { console.log(`[${regionPrefix}] ${line}`); } } }); } async function runCdkCommand(region, stackName, command, profile, options = {}, deploymentProfile) { const { verbose = false, parameters = [], includeDeps = false, context = [], executeChangeset = false, cdkOptions = "", signal, cloudAssemblyPath = null } = options; const cdkArgs = [command, "--profile", profile]; if (process.env.CDK_CLI_NOTICES !== "true") { cdkArgs.push("--no-notices"); } if (verbose) { cdkArgs.push("-v"); } if (!cloudAssemblyPath) { throw new Error("Cloud assembly path is required"); } cdkArgs.push("--app", cloudAssemblyPath); context.forEach((ctx) => { cdkArgs.push("--context", ctx); }); if (command === "deploy") { if (!includeDeps) cdkArgs.push("--exclusively"); if (!executeChangeset) cdkArgs.push("--no-execute"); cdkArgs.push("--require-approval=never"); if (executeChangeset) { cdkArgs.push("--progress", "events"); } } if (command === "diff") { if (!includeDeps) cdkArgs.push("--exclusively"); } parameters.forEach((param) => { if (includeDeps && !param.includes(":")) { cdkArgs.push("--parameters", `${stackName}:${param}`); } else { cdkArgs.push("--parameters", param); } }); if (cdkOptions) { const additionalArgs = cdkOptions.split(/\s+/).filter((arg) => arg); cdkArgs.push(...additionalArgs); } cdkArgs.push(stackName); process.env.AWS_REGION = region; process.env.FORCE_COLOR = "1"; if (command === "diff") { const regionPrefix = createRegionPrefix(deploymentProfile, region); try { const cdkProcess2 = $4({ signal, quiet: true })`cdk ${cdkArgs}`; const result = process.env.CDK_TIMEOUT ? await cdkProcess2.timeout(process.env.CDK_TIMEOUT) : await cdkProcess2; const filteredOutput = filterOutput( result.stdout + "\n" + result.stderr, command ); if (filteredOutput.trim()) { console.log(prefixDiffLines(filteredOutput.split("\n"), regionPrefix)); } return { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode }; } catch (error) { if (error instanceof Error && "stdout" in error) { const filteredOutput = filterOutput(error.stdout, command); if (filteredOutput.trim()) { console.log( prefixDiffLines(filteredOutput.split("\n"), regionPrefix) ); } } throw error; } } if (command === "deploy") { const regionPrefix = createRegionPrefix(deploymentProfile, region); const cdkProcess2 = $4({ signal, quiet: true })`cdk ${cdkArgs}`; cdkProcess2.stdout.on( "data", (data) => addRegionPrefixToOutput(data, regionPrefix) ); cdkProcess2.stderr.on( "data", (data) => addRegionPrefixToOutput(data, regionPrefix) ); const result = process.env.CDK_TIMEOUT ? await cdkProcess2.timeout(process.env.CDK_TIMEOUT) : await cdkProcess2; return { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode, stackArn: extractStackArn(result.stdout + "\n" + result.stderr) }; } const shouldQuiet = !verbose; const cdkProcess = $4({ signal, quiet: shouldQuiet })`cdk ${cdkArgs}`; try { const result = process.env.CDK_TIMEOUT ? await cdkProcess.timeout(process.env.CDK_TIMEOUT) : await cdkProcess; return { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode }; } catch (error) { if (shouldQuiet && error instanceof Error) { console.log(` --- CDK Error Details (${stackName}) ---`); try { const verboseProcess = $4({ signal, quiet: false })`cdk ${cdkArgs}`; await (process.env.CDK_TIMEOUT ? verboseProcess.timeout(process.env.CDK_TIMEOUT) : verboseProcess); } catch { } console.log(`--- End CDK Error Details --- `); } throw error; } } // src/core/orchestrator.ts async function executeCommand(deployment, args, command, options) { await runCdkCommand( deployment.region, deployment.constructId, command, deployment.profile || args.profile, options, deployment.profile || args.profile ); } async function deployStack(deployment, args, signal, cloudAssemblyPath = null) { const { region, constructId, stackName } = deployment; if (args.dryRun) { console.log(` [${region}] Would deploy: ${constructId}`); return { success: true, region, stackName }; } const startTime = Date.now(); try { const executorOptions = { ...args, signal, cloudAssemblyPath }; switch (args.mode) { case "diff": console.log(` [${region}] Showing differences for ${stackName}`); await executeCommand(deployment, args, "diff", executorOptions); break; case "changeset": console.log(` [${region}] Creating changeset for ${stackName}`); await executeCommand(deployment, args, "deploy", executorOptions); break; case "execute": console.log(` [${region}] Deploying ${stackName}`); await executeCommand(deployment, args, "deploy", { ...executorOptions, executeChangeset: true }); break; } const duration = ((Date.now() - startTime) / 1e3).toFixed(1); let completionMessage; switch (args.mode) { case "diff": completionMessage = `Changes detected (${duration}s)`; break; case "changeset": completionMessage = `Changeset created (${duration}s)`; break; case "execute": completionMessage = `Deployment completed (${duration}s)`; break; default: completionMessage = `Completed (${duration}s)`; } console.log(` [${region}] ${completionMessage}`); return { success: true, region, stackName, duration }; } catch (e) { logger.error(`[${region}] Failed: ${stackName}`); return { success: false, region, stackName, error: e }; } } async function deployToAllRegions(regions, args, signal) { const stackManager = new StackManager(); const stackConfig = await stackManager.loadConfig(); let deployments = []; const isMultiAccount = args.profile.includes(",") || args.profile.includes("*") || args.profile.includes("{"); if (isMultiAccount) { deployments = await resolveMultiAccountDeployments( args.profile, args.stackPattern, stackConfig, regions ); } else { if (stackConfig) { deployments = stackManager.resolveDeployments( args.stackPattern, stackConfig, regions ); if (deployments.length === 0) { logger.warn( "No matching stacks found in .cdko.json, falling back to traditional deployment" ); } } if (deployments.length === 0) { logger.info("Using traditional pattern-based deployment"); const stackPatterns = args.stackPattern.split(",").map((s) => s.trim()); for (const region of regions) { for (const stackName of stackPatterns) { deployments.push({ region, constructId: stackName, stackName, profile: args.profile }); } } } else { deployments = deployments.map((deployment) => ({ ...deployment, profile: args.profile })); } } const profileAssemblies = /* @__PURE__ */ new Map(); if (isMultiAccount) { const uniqueProfiles = [ ...new Set(deployments.map((d) => d.profile || args.profile)) ]; logger.phase("Synthesis", ""); const synthesisPromises = uniqueProfiles.map(async (profile) => { try { console.log(` [${profile}] Synthesizing cloud assembly...`); const profileDeployments = deployments.filter( (d) => d.profile === profile ); const stacksForProfile = [ ...new Set(profileDeployments.map((d) => d.constructId)) ]; const cloudAssembly = new CloudAssemblyManager(); const assemblyPath = await cloudAssembly.synthesize({ profile, environment: args.environment, outputDir: `cdk.out-${profile}`, stacks: stacksForProfile, exclusively: !args.includeDeps }); profileAssemblies.set(profile, assemblyPath); const shortPath = assemblyPath.split("/").pop() || assemblyPath; console.log(` [${profile}] Cloud assembly synthesized (${shortPath})`); return { profile, assemblyPath, success: true }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error(`[${profile}] Failed to synthesize: ${errorMessage}`); return { profile, error, success: false }; } }); const synthesisResults = await Promise.all(synthesisPromises); const failedSynthesis = synthesisResults.filter( (result) => !result.success ); if (failedSynthesis.length > 0) { throw new Error( `Failed to synthesize ${failedSynthesis.length} profile(s): ${failedSynthesis.map((f) => f.profile).join(", ")}` ); } } else { try { console.log(); logger.phase("Synthesis", ""); console.log(` [${args.profile}] Synthesizing cloud assembly...`); const stacksForProfile = [ ...new Set(deployments.map((d) => d.constructId)) ]; const cloudAssembly = new CloudAssemblyManager(); const assemblyPath = await cloudAssembly.synthesize({ profile: args.profile, environment: args.environment, stacks: stacksForProfile, exclusively: !args.includeDeps }); profileAssemblies.set(args.profile, assemblyPath); const shortPath = assemblyPath.split("/").pop() || assemblyPath; console.log( ` [${args.profile}] Cloud assembly synthesized (${shortPath})` ); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error(`Failed to synthesize cloud assembly: ${errorMessage}`); throw error; } } const results = []; console.log(); logger.info(`Planning to deploy ${deployments.length} stack(s):`); deployments.forEach((deployment) => { console.log(`\u2022 ${deployment.stackName} \u2192 ${deployment.region}`); }); console.log( `\u2022 Processing ${deployments.length} deployment(s) in ${args.sequential ? "sequential" : "parallel"}...` ); console.log(); logger.phase("Execution", ""); if (args.sequential) { for (const deployment of deployments) { const prefix = deployment.profile ? `${deployment.profile}/${deployment.region}` : deployment.region; console.log(` [${prefix}] Starting deployment...`); const assemblyPath = profileAssemblies.get( deployment.profile || args.profile ); results.push(await deployStack(deployment, args, signal, assemblyPath)); } } else { deployments.forEach((deployment) => { const prefix = deployment.profile ? `${deployment.profile}/${deployment.region}` : deployment.region; console.log(` [${prefix}] Starting deployment...`); }); const deploymentPromises = deployments.map((deployment) => { const assemblyPath = profileAssemblies.get( deployment.profile || args.profile ); return deployStack(deployment, args, signal, assemblyPath).then( (result) => ({ ...result, status: "fulfilled" }), (error) => ({ region: deployment.region, stackName: deployment.stackName, success: false, error, status: "rejected" }) ); }); const parallelResults = await Promise.all(deploymentPromises); results.push(...parallelResults); console.log(); results.forEach((result) => { if (result.success) { logger.success( `${result.region}: ${result.stackName} completed successfully` ); } else { const error = result.error; let errorMsg = "Unknown error"; if (error instanceof Error) { errorMsg = error.message; } else if (error && typeof error === "object" && "stderr" in error) { errorMsg = error.stderr; } else if (error && typeof error === "object" && "stdout" in error) { errorMsg = error.stdout; } else if (typeof error === "string") { errorMsg = error; } const lines = errorMsg.split("\n").filter((line) => line.trim()); const meaningfulError = lines.find( (line) => line.includes("no credentials have been configured") || line.includes("credentials have expired") || line.includes("Unable to locate credentials") || line.includes("AccessDenied") || line.includes("is not authorized") || line.includes("No stacks match") || line.includes("already exists") || line.includes("CloudFormation error") || line.includes("Error:") || line.includes("failed:") ) || lines.reverse().find( (line) => line.includes("no credentials") || line.includes("credentials") || !line.startsWith("[Warning") ) || lines[0] || "Check output above for details"; logger.error( `${result.region}: ${result.stackName} - ${meaningfulError.trim()}` ); } }); } return results; } async function resolveMultiAccountDeployments(profilePattern, stackPattern, stackConfig, requestedRegions) { const accountManager = new AccountManager(); const profiles = await accountManager.matchProfiles(profilePattern); if (profiles.length === 0) { throw new Error(`No profiles found matching pattern: ${profilePattern}`); } const accountInfo = await accountManager.getMultiAccountInfo(profiles); if (accountInfo.length === 0) { throw new Error("Failed to authenticate any profiles"); } const deploymentTargets = accountManager.createDeploymentTargets( accountInfo, requestedRegions ); const stackManager = new StackManager(); const matchedStacks = stackManager.matchStacks(stackPattern, stackConfig); const allDeployments = []; if (matchedStacks.length === 0) { deploymentTargets.forEach(({ profile, accountId, region }) => { allDeployments.push({ profile, accountId, region, constructId: stackPattern, stackName: stackPattern, source: "pattern" }); }); } else { matchedStacks.forEach((stackGroup) => { deploymentTargets.forEach(({ profile, accountId, region, key }) => { if (stackGroup.deployments[key]) { const deployment = stackGroup.deployments[key]; allDeployments.push({ profile, accountId, region, constructId: deployment.constructId, stackName: stackGroup.name, source: "configured" }); } else { const unknownRegionKey = `${accountId}/unknown-region`; if (stackGroup.deployments[unknownRegionKey]) { const deployment = stackGroup.deployments[unknownRegionKey]; allDeployments.push({ profile, accountId, region, constructId: deployment.constructId, stackName: stackGroup.name, source: "region-agnostic" }); } } }); }); } if (allDeployments.length === 0) { logger.warn( "No deployments resolved - check stack configuration and account/region combinations" ); return []; } logMultiAccountDeploymentSummary(allDeployments); return allDeployments; } function logMultiAccountDeploymentSummary(deployments) { const accounts = [...new Set(deployments.map((d) => d.accountId))]; logger.info( `${deployments.length} deployment(s) across ${accounts.length} account(s)` ); } // src/utils/prerequisites.ts import { $ as $5 } from "zx"; async function checkPrerequisites() { const requiredTools = ["aws", "cdk"]; const missing = []; for (const tool of requiredTools) { try { await $5`which ${tool}`.quiet(); } catch { missing.push(tool); } } if (missing.length > 0) { logger.error(`Missing required tools: ${missing.join(", ")}`); logger.info("Please install the missing tools to continue"); process.exit(1); } try { const cdkVersion = await $5`cdk --version`.quiet(); logger.subheader(`Using CDK version: ${cdkVersion.toString().trim()}`); } catch { } } export { AccountManager, CloudAssemblyManager, StackManager, checkPrerequisites, deployToAllRegions, logger, matchPattern, runCdkCommand }; //# sourceMappingURL=index.js.map