packages/@aws-cdk/toolkit-lib/lib/api/cloud-assembly/stack-collection.ts (98 lines of code) (raw):

import type * as cxapi from '@aws-cdk/cx-api'; import { SynthesisMessageLevel } from '@aws-cdk/cx-api'; import { AssemblyError, ToolkitError } from '../toolkit-error'; import type { IStackAssembly } from './stack-assembly'; import { type StackDetails } from '../../payloads/stack-details'; /** * A collection of stacks and related artifacts * * In practice, not all artifacts in the CloudAssembly are created equal; * stacks can be selected independently, but other artifacts such as asset * bundles cannot. */ export class StackCollection { constructor(public readonly assembly: IStackAssembly, public readonly stackArtifacts: cxapi.CloudFormationStackArtifact[]) { } public get stackCount() { return this.stackArtifacts.length; } public get firstStack() { if (this.stackCount < 1) { throw new ToolkitError('StackCollection contains no stack artifacts (trying to access the first one)'); } return this.stackArtifacts[0]; } public get stackIds(): string[] { return this.stackArtifacts.map(s => s.id); } public get hierarchicalIds(): string[] { return this.stackArtifacts.map(s => s.hierarchicalId); } public withDependencies(): StackDetails[] { const allData: StackDetails[] = []; for (const stack of this.stackArtifacts) { const data: StackDetails = { id: stack.displayName ?? stack.id, name: stack.stackName, environment: stack.environment, dependencies: [], }; for (const dependencyId of stack.dependencies.map(x => x.id)) { if (dependencyId.includes('.assets')) { continue; } const depStack = this.assembly.stackById(dependencyId); if (depStack.firstStack.dependencies.filter((dep) => !(dep.id).includes('.assets')).length > 0) { for (const stackDetail of depStack.withDependencies()) { data.dependencies.push({ id: stackDetail.id, dependencies: stackDetail.dependencies, }); } } else { data.dependencies.push({ id: depStack.firstStack.displayName ?? depStack.firstStack.id, dependencies: [], }); } } allData.push(data); } return allData; } public reversed() { const arts = [...this.stackArtifacts]; arts.reverse(); return new StackCollection(this.assembly, arts); } public filter(predicate: (art: cxapi.CloudFormationStackArtifact) => boolean): StackCollection { return new StackCollection(this.assembly, this.stackArtifacts.filter(predicate)); } public concat(...others: StackCollection[]): StackCollection { return new StackCollection(this.assembly, this.stackArtifacts.concat(...others.map(o => o.stackArtifacts))); } /** * Extracts 'aws:cdk:warning|info|error' metadata entries from the stack synthesis */ public async validateMetadata( failAt: 'warn' | 'error' | 'none' = 'error', logger: (level: 'info' | 'error' | 'warn', msg: cxapi.SynthesisMessage) => Promise<void> = async () => { }, ) { let warnings = false; let errors = false; for (const stack of this.stackArtifacts) { for (const message of stack.messages) { switch (message.level) { case SynthesisMessageLevel.WARNING: warnings = true; await logger('warn', message); break; case SynthesisMessageLevel.ERROR: errors = true; await logger('error', message); break; case SynthesisMessageLevel.INFO: await logger('info', message); break; } } } if (errors && failAt != 'none') { throw AssemblyError.withStacks('Found errors', this.stackArtifacts); } if (warnings && failAt === 'warn') { throw AssemblyError.withStacks('Found warnings (--strict mode)', this.stackArtifacts); } } }