diff --git a/aws-cdk.code-workspace b/aws-cdk.code-workspace index 406bc32a95401..49a0a85e992a7 100644 --- a/aws-cdk.code-workspace +++ b/aws-cdk.code-workspace @@ -31,6 +31,7 @@ "name": "aws-custom-resource-sdk-adapter", "rootPath": "packages/@aws-cdk/aws-custom-resource-sdk-adapter" }, + { "name": "toolkit", "rootPath": "packages/@aws-cdk/toolkit" }, { "name": "user-input-gen", "rootPath": "tools/@aws-cdk/user-input-gen" } ] }, diff --git a/packages/@aws-cdk/toolkit/.gitignore b/packages/@aws-cdk/toolkit/.gitignore index da75d711cfa41..30b8852720e25 100644 --- a/packages/@aws-cdk/toolkit/.gitignore +++ b/packages/@aws-cdk/toolkit/.gitignore @@ -26,6 +26,10 @@ build-info.json lib/**/*.wasm lib/**/*.yaml +# Include test resources +!test/_fixtures/**/app.js +!test/_fixtures/**/cdk.out + # Include config files !.eslintrc.js !jest.config.js diff --git a/packages/@aws-cdk/toolkit/jest.config.js b/packages/@aws-cdk/toolkit/jest.config.js index 69a39a35b373a..ad0e255a79aba 100644 --- a/packages/@aws-cdk/toolkit/jest.config.js +++ b/packages/@aws-cdk/toolkit/jest.config.js @@ -3,7 +3,9 @@ module.exports = { ...baseConfig, coverageThreshold: { global: { - ...baseConfig.coverageThreshold.global, + // this is very sad but we will get better + branches: 27, + statements: 53, }, }, }; diff --git a/packages/@aws-cdk/toolkit/lib/actions/synth/index.ts b/packages/@aws-cdk/toolkit/lib/actions/synth/index.ts index 2cb1420cc0987..2cbdea734ee6e 100644 --- a/packages/@aws-cdk/toolkit/lib/actions/synth/index.ts +++ b/packages/@aws-cdk/toolkit/lib/actions/synth/index.ts @@ -4,7 +4,7 @@ export interface SynthOptions { /** * Select the stacks */ - readonly stacks: StackSelector; + readonly stacks?: StackSelector; /** * After synthesis, validate stacks with the "validateOnSynth" attribute set (can also be controlled with CDK_VALIDATION) diff --git a/packages/@aws-cdk/toolkit/lib/actions/watch/index.ts b/packages/@aws-cdk/toolkit/lib/actions/watch/index.ts index 1b0d5c51aa5ab..69cd94fc4f22e 100644 --- a/packages/@aws-cdk/toolkit/lib/actions/watch/index.ts +++ b/packages/@aws-cdk/toolkit/lib/actions/watch/index.ts @@ -38,11 +38,11 @@ export interface WatchOptions extends BaseDeployOptions { readonly watchDir?: string; /** - * The output to write CloudFormation template to + * The output directory to write CloudFormation template to * * @deprecated this should be grabbed from the cloud assembly itself */ - readonly output?: string; + readonly outdir?: string; } export function patternsArrayForWatch( diff --git a/packages/@aws-cdk/toolkit/lib/api/cloud-assembly/private/index.ts b/packages/@aws-cdk/toolkit/lib/api/cloud-assembly/private/index.ts new file mode 100644 index 0000000000000..1733ca0c300d3 --- /dev/null +++ b/packages/@aws-cdk/toolkit/lib/api/cloud-assembly/private/index.ts @@ -0,0 +1,5 @@ +export * from './context-aware-source'; +export * from './exec'; +export * from './prepare-source'; +export * from './source-builder'; +export * from './stack-selectors'; diff --git a/packages/@aws-cdk/toolkit/lib/api/cloud-assembly/private/prepare-source.ts b/packages/@aws-cdk/toolkit/lib/api/cloud-assembly/private/prepare-source.ts index 4fc5e33911c92..ba1715a105042 100644 --- a/packages/@aws-cdk/toolkit/lib/api/cloud-assembly/private/prepare-source.ts +++ b/packages/@aws-cdk/toolkit/lib/api/cloud-assembly/private/prepare-source.ts @@ -4,17 +4,24 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs-extra'; import { lte } from 'semver'; -import type { AppSynthOptions } from './source-builder'; import { prepareDefaultEnvironment as oldPrepare, prepareContext, spaceAvailableForContext, Settings, loadTree, some, splitBySize, versionNumber } from '../../../api/aws-cdk'; import { ToolkitServices } from '../../../toolkit/private'; import { ToolkitError } from '../../errors'; import { ActionAwareIoHost, asLogger, error } from '../../io/private'; +import type { AppSynthOptions } from '../source-builder'; export { guessExecutable } from '../../../api/aws-cdk'; type Env = { [key: string]: string }; type Context = { [key: string]: any }; +/** + * Turn the given optional output directory into a fixed output directory + */ +export function determineOutputDirectory(outdir?: string) { + return outdir ?? fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'cdk.out')); +} + /** * If we don't have region/account defined in context, we fall back to the default SDK behavior * where region is retrieved from ~/.aws/config and account is based on default credentials provider diff --git a/packages/@aws-cdk/toolkit/lib/api/cloud-assembly/private/source-builder.ts b/packages/@aws-cdk/toolkit/lib/api/cloud-assembly/private/source-builder.ts index 9a819959858aa..0f97d02e1514c 100644 --- a/packages/@aws-cdk/toolkit/lib/api/cloud-assembly/private/source-builder.ts +++ b/packages/@aws-cdk/toolkit/lib/api/cloud-assembly/private/source-builder.ts @@ -1,47 +1,13 @@ -import * as cxapi from '@aws-cdk/cx-api'; - import * as fs from 'fs-extra'; import type { ICloudAssemblySource } from '../'; import { ContextAwareCloudAssembly, ContextAwareCloudAssemblyProps } from './context-aware-source'; import { execInChildProcess } from './exec'; -import { assemblyFromDirectory, changeDir, guessExecutable, prepareDefaultEnvironment, withContext, withEnv } from './prepare-source'; +import { assemblyFromDirectory, changeDir, determineOutputDirectory, guessExecutable, prepareDefaultEnvironment, withContext, withEnv } from './prepare-source'; import { ToolkitServices } from '../../../toolkit/private'; import { Context, ILock, RWLock } from '../../aws-cdk'; import { ToolkitError } from '../../errors'; import { debug } from '../../io/private'; - -/** - * Configuration for creating a CLI from an AWS CDK App directory - */ -export interface CdkAppSourceProps { - /** - * @default - current working directory - */ - readonly workingDirectory?: string; - - /** - * Emits the synthesized cloud assembly into a directory - * - * @default cdk.out - */ - readonly output?: string; - - /** - * Perform context lookups. - * - * Synthesis fails if this is disabled and context lookups need to be performed. - * - * @default true - */ - readonly lookups?: boolean; - - /** - * Options that are passed through the context to a CDK app on synth - */ - readonly synthOptions?: AppSynthOptions; -} - -export type AssemblyBuilder = (context: Record) => Promise; +import { AssemblyBuilder, CdkAppSourceProps } from '../source-builder'; export abstract class CloudAssemblySourceBuilder { @@ -69,10 +35,14 @@ export abstract class CloudAssemblySourceBuilder { return new ContextAwareCloudAssembly( { produce: async () => { - const env = await prepareDefaultEnvironment(services, { outdir: props.output }); + const outdir = determineOutputDirectory(props.outdir); + const env = await prepareDefaultEnvironment(services, { outdir }); return changeDir(async () => withContext(context.all, env, props.synthOptions ?? {}, async (envWithContext, ctx) => - withEnv(envWithContext, () => builder(ctx)), + withEnv(envWithContext, () => builder({ + outdir, + context: ctx, + })), ), props.workingDirectory); }, }, @@ -133,7 +103,7 @@ export abstract class CloudAssemblySourceBuilder { // } const commandLine = await guessExecutable(app); - const outdir = props.output ?? 'cdk.out'; + const outdir = props.outdir ?? 'cdk.out'; try { fs.mkdirpSync(outdir); @@ -158,65 +128,3 @@ export abstract class CloudAssemblySourceBuilder { } } -/** - * Settings that are passed to a CDK app via the context - */ -export interface AppSynthOptions { - /** - * Debug the CDK app. - * Logs additional information during synthesis, such as creation stack traces of tokens. - * This also sets the `CDK_DEBUG` env variable and will slow down synthesis. - * - * @default false - */ - readonly debug?: boolean; - - /** - * Enables the embedding of the "aws:cdk:path" in CloudFormation template metadata. - * - * @default true - */ - readonly pathMetadata?: boolean; - - /** - * Enable the collection and reporting of version information. - * - * @default true - */ - readonly versionReporting?: boolean; - - /** - * Whe enabled, `aws:asset:xxx` metadata entries are added to the template. - * - * Disabling this can be useful in certain cases like integration tests. - * - * @default true - */ - readonly assetMetadata?: boolean; - - /** - * Enable asset staging. - * - * Disabling asset staging means that copyable assets will not be copied to the - * output directory and will be referenced with absolute paths. - * - * Not copied to the output directory: this is so users can iterate on the - * Lambda source and run SAM CLI without having to re-run CDK (note: we - * cannot achieve this for bundled assets, if assets are bundled they - * will have to re-run CDK CLI to re-bundle updated versions). - * - * Absolute path: SAM CLI expects `cwd`-relative paths in a resource's - * `aws:asset:path` metadata. In order to be predictable, we will always output - * absolute paths. - * - * @default true - */ - readonly assetStaging?: boolean; - - /** - * Select which stacks should have asset bundling enabled - * - * @default ["**"] - all stacks - */ - readonly bundlingForStacks?: string; -} diff --git a/packages/@aws-cdk/toolkit/lib/api/cloud-assembly/private/stack-selectors.ts b/packages/@aws-cdk/toolkit/lib/api/cloud-assembly/private/stack-selectors.ts new file mode 100644 index 0000000000000..31f114b9d82d8 --- /dev/null +++ b/packages/@aws-cdk/toolkit/lib/api/cloud-assembly/private/stack-selectors.ts @@ -0,0 +1,5 @@ +import { StackSelectionStrategy, StackSelector } from '../stack-selector'; + +export const ALL_STACKS: StackSelector = { + strategy: StackSelectionStrategy.ALL_STACKS, +}; diff --git a/packages/@aws-cdk/toolkit/lib/api/cloud-assembly/source-builder.ts b/packages/@aws-cdk/toolkit/lib/api/cloud-assembly/source-builder.ts new file mode 100644 index 0000000000000..24f091846da81 --- /dev/null +++ b/packages/@aws-cdk/toolkit/lib/api/cloud-assembly/source-builder.ts @@ -0,0 +1,109 @@ +import type * as cxapi from '@aws-cdk/cx-api'; + +export interface AppProps { + /** + * The output directory into which to the builder app will emit synthesized artifacts. + */ + readonly outdir?: string; + + /** + * The context provided tp the builder app to synthesize the Cloud Assembly. + */ + readonly context?: { [key: string]: any }; +} + +export type AssemblyBuilder = (props: AppProps) => Promise; + +/** + * Configuration for creating a CLI from an AWS CDK App directory + */ +export interface CdkAppSourceProps { + /** + * @default - current working directory + */ + readonly workingDirectory?: string; + + /** + * Emits the synthesized cloud assembly into a directory + * + * @default cdk.out + */ + readonly outdir?: string; + + /** + * Perform context lookups. + * + * Synthesis fails if this is disabled and context lookups need to be performed. + * + * @default true + */ + readonly lookups?: boolean; + + /** + * Options that are passed through the context to a CDK app on synth + */ + readonly synthOptions?: AppSynthOptions; +} + +/** + * Settings that are passed to a CDK app via the context + */ +export interface AppSynthOptions { + /** + * Debug the CDK app. + * Logs additional information during synthesis, such as creation stack traces of tokens. + * This also sets the `CDK_DEBUG` env variable and will slow down synthesis. + * + * @default false + */ + readonly debug?: boolean; + + /** + * Enables the embedding of the "aws:cdk:path" in CloudFormation template metadata. + * + * @default true + */ + readonly pathMetadata?: boolean; + + /** + * Enable the collection and reporting of version information. + * + * @default true + */ + readonly versionReporting?: boolean; + + /** + * Whe enabled, `aws:asset:xxx` metadata entries are added to the template. + * + * Disabling this can be useful in certain cases like integration tests. + * + * @default true + */ + readonly assetMetadata?: boolean; + + /** + * Enable asset staging. + * + * Disabling asset staging means that copyable assets will not be copied to the + * output directory and will be referenced with absolute paths. + * + * Not copied to the output directory: this is so users can iterate on the + * Lambda source and run SAM CLI without having to re-run CDK (note: we + * cannot achieve this for bundled assets, if assets are bundled they + * will have to re-run CDK CLI to re-bundle updated versions). + * + * Absolute path: SAM CLI expects `cwd`-relative paths in a resource's + * `aws:asset:path` metadata. In order to be predictable, we will always output + * absolute paths. + * + * @default true + */ + readonly assetStaging?: boolean; + + /** + * Select which stacks should have asset bundling enabled + * + * @default ["**"] - all stacks + */ + readonly bundlingForStacks?: string; +} diff --git a/packages/@aws-cdk/toolkit/lib/api/io/index.ts b/packages/@aws-cdk/toolkit/lib/api/io/index.ts index 7ee0b514c8639..ac3a3d5b5a9c8 100644 --- a/packages/@aws-cdk/toolkit/lib/api/io/index.ts +++ b/packages/@aws-cdk/toolkit/lib/api/io/index.ts @@ -1 +1,2 @@ export * from './io-host'; +export * from './io-message'; diff --git a/packages/@aws-cdk/toolkit/lib/api/io/io-host.ts b/packages/@aws-cdk/toolkit/lib/api/io/io-host.ts index 03aff9f114819..90deee3d172e7 100644 --- a/packages/@aws-cdk/toolkit/lib/api/io/io-host.ts +++ b/packages/@aws-cdk/toolkit/lib/api/io/io-host.ts @@ -1,83 +1,4 @@ -import type { ToolkitAction } from '../../toolkit'; - -/** - * The reporting level of the message. - * All messages are always reported, it's up to the IoHost to decide what to log. - */ -export type IoMessageLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace'; - -/** - * Valid reporting categories for messages. - */ -export type IoMessageCodeCategory = 'TOOLKIT' | 'SDK' | 'ASSETS' | 'ASSEMBLY'; - -/** - * Code level matching the reporting level. - */ -export type IoCodeLevel = 'E' | 'W' | 'I'; - -/** - * A message code at a specific level - */ -export type IoMessageSpecificCode = `CDK_${IoMessageCodeCategory}_${L}${number}${number}${number}${number}`; - -/** - * A valid message code - */ -export type IoMessageCode = IoMessageSpecificCode; - -export interface IoMessage { - /** - * The time the message was emitted. - */ - readonly time: Date; - - /** - * The log level of the message. - */ - readonly level: IoMessageLevel; - - /** - * The action that triggered the message. - */ - readonly action: ToolkitAction; - - /** - * A short message code uniquely identifying a message type using the format CDK_[CATEGORY]_[E/W/I][0000-9999]. - * - * The level indicator follows these rules: - * - 'E' for error level messages - * - 'W' for warning level messages - * - 'I' for info/debug/trace level messages - * - * Codes ending in 000 0 are generic messages, while codes ending in 0001-9999 are specific to a particular message. - * The following are examples of valid and invalid message codes: - * ```ts - * 'CDK_ASSETS_I0000' // valid: generic assets info message - * 'CDK_TOOLKIT_E0002' // valid: specific toolkit error message - * 'CDK_SDK_W0023' // valid: specific sdk warning message - * ``` - */ - readonly code: IoMessageCode; - - /** - * The message text. - * This is safe to print to an end-user. - */ - readonly message: string; - - /** - * The data attached to the message. - */ - readonly data?: T; -} - -export interface IoRequest extends IoMessage { - /** - * The default response that will be used if no data is returned. - */ - readonly defaultResponse: U; -} +import { IoMessage, IoRequest } from './io-message'; export interface IIoHost { /** diff --git a/packages/@aws-cdk/toolkit/lib/api/io/io-message.ts b/packages/@aws-cdk/toolkit/lib/api/io/io-message.ts new file mode 100644 index 0000000000000..99e772ff83610 --- /dev/null +++ b/packages/@aws-cdk/toolkit/lib/api/io/io-message.ts @@ -0,0 +1,112 @@ +import { ToolkitAction } from '../../toolkit'; + +/** + * The reporting level of the message. + * All messages are always reported, it's up to the IoHost to decide what to log. + */ +export type IoMessageLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace'; + +/** + * Valid reporting categories for messages. + */ +export type IoMessageCodeCategory = 'TOOLKIT' | 'SDK' | 'ASSETS' | 'ASSEMBLY'; + +/** + * Code level matching the reporting level. + */ +export type IoCodeLevel = 'E' | 'W' | 'I'; + +/** + * A message code at a specific level + */ +export type IoMessageSpecificCode = `CDK_${IoMessageCodeCategory}_${L}${number}${number}${number}${number}`; + +/** + * A valid message code + */ +export type IoMessageCode = IoMessageSpecificCode; + +export interface IoMessage { + /** + * The time the message was emitted. + */ + readonly time: Date; + + /** + * The log level of the message. + */ + readonly level: IoMessageLevel; + + /** + * The action that triggered the message. + */ + readonly action: ToolkitAction; + + /** + * A short message code uniquely identifying a message type using the format CDK_[CATEGORY]_[E/W/I][0000-9999]. + * + * The level indicator follows these rules: + * - 'E' for error level messages + * - 'W' for warning level messages + * - 'I' for info/debug/trace level messages + * + * Codes ending in 000 0 are generic messages, while codes ending in 0001-9999 are specific to a particular message. + * The following are examples of valid and invalid message codes: + * ```ts + * 'CDK_ASSETS_I0000' // valid: generic assets info message + * 'CDK_TOOLKIT_E0002' // valid: specific toolkit error message + * 'CDK_SDK_W0023' // valid: specific sdk warning message + * ``` + */ + readonly code: IoMessageCode; + + /** + * The message text. + * This is safe to print to an end-user. + */ + readonly message: string; + + /** + * The data attached to the message. + */ + readonly data?: T; +} + +export interface IoRequest extends IoMessage { + /** + * The default response that will be used if no data is returned. + */ + readonly defaultResponse: U; +} + +/** + * Keep this list ordered from most to least verbose. + * Every level "includes" all of the levels below it. + * This is used to compare levels of messages to determine what should be logged. + */ +const levels = [ + 'trace', + 'debug', + 'info', + 'warn', + 'error', +] as const; + +// compare levels helper +// helper to convert the array into a map with numbers +const orderedLevels: Record = Object.fromEntries(Object.entries(levels).map(a => a.reverse())); +function compareFn(a: IoMessageLevel, b: IoMessageLevel): number { + return orderedLevels[a] - orderedLevels[b]; +} + +/** + * Determines if a message is relevant for the given log level. + * + * @param msg The message to compare. + * @param level The level to compare against. + * @returns true if the message is relevant for the given level. + */ +export function isMessageRelevantForLevel(msg: { level: IoMessageLevel}, level: IoMessageLevel): boolean { + return compareFn(msg.level, level) >= 0; +} + diff --git a/packages/@aws-cdk/toolkit/lib/api/io/private/logger.ts b/packages/@aws-cdk/toolkit/lib/api/io/private/logger.ts index 0d3632d504837..04ab089d5fc42 100644 --- a/packages/@aws-cdk/toolkit/lib/api/io/private/logger.ts +++ b/packages/@aws-cdk/toolkit/lib/api/io/private/logger.ts @@ -1,10 +1,11 @@ import * as util from 'node:util'; import type { Logger } from '@smithy/types'; -import type { IIoHost, IoMessage, IoMessageCodeCategory, IoMessageLevel, IoRequest } from '../io-host'; +import type { IoMessage, IoMessageCodeCategory, IoMessageLevel, IoRequest } from '../io-message'; import { debug, error, info, messageCode, trace, warn } from './messages'; -import { ActionAwareIoHost } from './types'; +import type { ActionAwareIoHost } from './types'; import type { ToolkitAction } from '../../../toolkit'; import { formatSdkLoggerContent } from '../../aws-cdk'; +import type { IIoHost } from '../io-host'; export function withAction(ioHost: IIoHost, action: ToolkitAction) { return { diff --git a/packages/@aws-cdk/toolkit/lib/api/io/private/messages.ts b/packages/@aws-cdk/toolkit/lib/api/io/private/messages.ts index 55cfde9ef7248..9bf3a388c2bd5 100644 --- a/packages/@aws-cdk/toolkit/lib/api/io/private/messages.ts +++ b/packages/@aws-cdk/toolkit/lib/api/io/private/messages.ts @@ -1,5 +1,5 @@ import * as chalk from 'chalk'; -import type { IoMessageCode, IoMessageCodeCategory, IoMessageLevel } from '../io-host'; +import type { IoMessageCode, IoMessageCodeCategory, IoMessageLevel } from '../io-message'; import type { ActionLessMessage, ActionLessRequest, Optional, SimplifiedMessage } from './types'; /** diff --git a/packages/@aws-cdk/toolkit/lib/api/io/private/types.ts b/packages/@aws-cdk/toolkit/lib/api/io/private/types.ts index 27f14c55e8d0a..9464f8a670ec4 100644 --- a/packages/@aws-cdk/toolkit/lib/api/io/private/types.ts +++ b/packages/@aws-cdk/toolkit/lib/api/io/private/types.ts @@ -1,4 +1,5 @@ -import { IIoHost, IoMessage, IoRequest } from '../io-host'; +import { IIoHost } from '../io-host'; +import { IoMessage, IoRequest } from '../io-message'; export type Optional = Pick, K> & Omit; export type SimplifiedMessage = Pick, 'level' | 'code' | 'message' | 'data'>; diff --git a/packages/@aws-cdk/toolkit/lib/toolkit/toolkit.ts b/packages/@aws-cdk/toolkit/lib/toolkit/toolkit.ts index b781c392035fd..2cc77d2adaa61 100644 --- a/packages/@aws-cdk/toolkit/lib/toolkit/toolkit.ts +++ b/packages/@aws-cdk/toolkit/lib/toolkit/toolkit.ts @@ -15,10 +15,10 @@ import { patternsArrayForWatch, WatchOptions } from '../actions/watch'; import { SdkOptions } from '../api/aws-auth'; import { DEFAULT_TOOLKIT_STACK_NAME, SdkProvider, SuccessfulDeployStackResult, StackCollection, Deployments, HotswapMode, StackActivityProgress, ResourceMigrator, obscureTemplate, serializeStructure, tagsForStack, CliIoHost, validateSnsTopicArn, Concurrency, WorkGraphBuilder, AssetBuildNode, AssetPublishNode, StackNode } from '../api/aws-cdk'; import { CachedCloudAssemblySource, IdentityCloudAssemblySource, StackAssembly, ICloudAssemblySource, StackSelectionStrategy } from '../api/cloud-assembly'; -import { CloudAssemblySourceBuilder } from '../api/cloud-assembly/private/source-builder'; +import { ALL_STACKS, CloudAssemblySourceBuilder } from '../api/cloud-assembly/private'; import { ToolkitError } from '../api/errors'; import { IIoHost, IoMessageCode, IoMessageLevel } from '../api/io'; -import { asSdkLogger, withAction, Timer, confirm, data, error, highlight, info, success, warn, ActionAwareIoHost, debug } from '../api/io/private'; +import { asSdkLogger, withAction, Timer, confirm, error, highlight, info, success, warn, ActionAwareIoHost, debug } from '../api/io/private'; /** * The current action being performed by the CLI. 'none' represents the absence of an action. @@ -38,7 +38,7 @@ export interface ToolkitOptions { /** * The IoHost implementation, handling the inline interactions between the Toolkit and an integration. */ - // ioHost: IIoHost; + ioHost?: IIoHost; /** * Configuration options for the SDK. @@ -78,10 +78,7 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab public constructor(private readonly props: ToolkitOptions = {}) { super(); - // @todo open ioHost up - this.ioHost = CliIoHost.getIoHost(); - // this.ioHost = options.ioHost; - + this.ioHost = props.ioHost ?? CliIoHost.getIoHost(); this.toolkitStackName = props.toolkitStackName ?? DEFAULT_TOOLKIT_STACK_NAME; } @@ -121,26 +118,38 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab /** * Synth Action */ - public async synth(cx: ICloudAssemblySource, options: SynthOptions): Promise { + public async synth(cx: ICloudAssemblySource, options: SynthOptions = {}): Promise { const ioHost = withAction(this.ioHost, 'synth'); const assembly = await this.assemblyFromSource(cx); - const stacks = assembly.selectStacksV2(options.stacks); + const stacks = assembly.selectStacksV2(options.stacks ?? ALL_STACKS); const autoValidateStacks = options.validateStacks ? [assembly.selectStacksForValidation()] : []; await this.validateStacksMetadata(stacks.concat(...autoValidateStacks), ioHost); // if we have a single stack, print it to STDOUT + const message = `Successfully synthesized to ${chalk.blue(path.resolve(stacks.assembly.directory))}`; + const assemblyData = { + assemblyDirectory: stacks.assembly.directory, + stacksCount: stacks.stackCount, + stackIds: stacks.hierarchicalIds, + }; + if (stacks.stackCount === 1) { - const template = stacks.firstStack?.template; + const firstStack = stacks.firstStack!; + const template = firstStack.template; const obscuredTemplate = obscureTemplate(template); - await ioHost.notify(info('', 'CDK_TOOLKIT_I0001', { - raw: template, - json: serializeStructure(obscuredTemplate, true), - yaml: serializeStructure(obscuredTemplate, false), - }, - )); + await ioHost.notify(info(message, 'CDK_TOOLKIT_I0001', { + ...assemblyData, + stack: { + stackName: firstStack.stackName, + hierarchicalId: firstStack.hierarchicalId, + template, + stringifiedJson: serializeStructure(obscuredTemplate, true), + stringifiedYaml: serializeStructure(obscuredTemplate, false), + }, + })); } else { // not outputting template to stdout, let's explain things to the user a little bit... - await ioHost.notify(success(`Successfully synthesized to ${chalk.blue(path.resolve(stacks.assembly.directory))}`)); + await ioHost.notify(success(message, 'CDK_TOOLKIT_I0002', assemblyData)); await ioHost.notify(info(`Supply a stack id (${stacks.stackArtifacts.map((s) => chalk.green(s.hierarchicalId)).join(', ')}) to display its template.`)); } @@ -416,7 +425,7 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab await ioHost.notify(info('Stack ARN:')); - await ioHost.notify(data(deployResult.stackArn)); + await ioHost.notify(info(deployResult.stackArn)); } catch (e: any) { // It has to be exactly this string because an integration test tests for // "bold(stackname) failed: ResourceNotReady: " @@ -518,7 +527,7 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab const watchExcludes = patternsArrayForWatch(options.exclude, { rootDir, returnRootDirIfEmpty: false, - }).concat(`${options.output}/**`, '**/.*', '**/.*/**', '**/node_modules/**'); + }).concat(`${options.outdir}/**`, '**/.*', '**/.*/**', '**/node_modules/**'); await ioHost.notify(debug(`'exclude' patterns for 'watch': ${watchExcludes}`)); // Since 'cdk deploy' is a relatively slow operation for a 'watch' process, diff --git a/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/cdk.out/Stack1.assets.json b/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/cdk.out/Stack1.assets.json new file mode 100644 index 0000000000000..8d05af80a77c4 --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/cdk.out/Stack1.assets.json @@ -0,0 +1,19 @@ +{ + "version": "39.0.0", + "files": { + "6cf0c6d5d33f7914e048b4435b7ffc1909cdec43efb95fcde227762c2f0effd1": { + "source": { + "path": "Stack1.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "6cf0c6d5d33f7914e048b4435b7ffc1909cdec43efb95fcde227762c2f0effd1.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/cdk.out/Stack1.template.json b/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/cdk.out/Stack1.template.json new file mode 100644 index 0000000000000..20847c180765f --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/cdk.out/Stack1.template.json @@ -0,0 +1,322 @@ +{ + "Resources": { + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Metadata": { + "aws:cdk:path": "Stack1/MyBucket/Resource" + } + }, + "CDKMetadata": { + "Type": "AWS::CDK::Metadata", + "Properties": { + "Analytics": "v2:deflate64:H4sIAAAAAAAA/8vLT0nVyyrWLzMy0DM01jNUzCrOzNQtKs0rycxN1QuC0ADPiCvwJQAAAA==" + }, + "Metadata": { + "aws:cdk:path": "Stack1/CDKMetadata/Default" + }, + "Condition": "CDKMetadataAvailable" + } + }, + "Conditions": { + "CDKMetadataAvailable": { + "Fn::Or": [ + { + "Fn::Or": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "af-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-east-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-northeast-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-northeast-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-northeast-3" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-south-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-3" + ] + } + ] + }, + { + "Fn::Or": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-4" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ca-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ca-west-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "cn-north-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "cn-northwest-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-central-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-north-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-south-2" + ] + } + ] + }, + { + "Fn::Or": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-west-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-west-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-west-3" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "il-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "me-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "me-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "sa-east-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-east-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-east-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-west-1" + ] + } + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-west-2" + ] + } + ] + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/cdk.out/cdk.out b/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/cdk.out/cdk.out new file mode 100644 index 0000000000000..91e1a8b9901d5 --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/cdk.out/cdk.out @@ -0,0 +1 @@ +{"version":"39.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/cdk.out/manifest.json b/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/cdk.out/manifest.json new file mode 100644 index 0000000000000..09af0c92cf2d9 --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/cdk.out/manifest.json @@ -0,0 +1,77 @@ +{ + "version": "39.0.0", + "artifacts": { + "Stack1.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "Stack1.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "Stack1": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "Stack1.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/6cf0c6d5d33f7914e048b4435b7ffc1909cdec43efb95fcde227762c2f0effd1.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "Stack1.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "Stack1.assets" + ], + "metadata": { + "/Stack1/MyBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyBucketF68F3FF0" + } + ], + "/Stack1/CDKMetadata/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "CDKMetadata" + } + ], + "/Stack1/CDKMetadata/Condition": [ + { + "type": "aws:cdk:logicalId", + "data": "CDKMetadataAvailable" + } + ], + "/Stack1/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/Stack1/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "Stack1" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/cdk.out/tree.json b/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/cdk.out/tree.json new file mode 100644 index 0000000000000..8416e84df186b --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/cdk.out/tree.json @@ -0,0 +1,95 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Stack1": { + "id": "Stack1", + "path": "Stack1", + "children": { + "MyBucket": { + "id": "MyBucket", + "path": "Stack1/MyBucket", + "children": { + "Resource": { + "id": "Resource", + "path": "Stack1/MyBucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + }, + "CDKMetadata": { + "id": "CDKMetadata", + "path": "Stack1/CDKMetadata", + "children": { + "Default": { + "id": "Default", + "path": "Stack1/CDKMetadata/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + }, + "Condition": { + "id": "Condition", + "path": "Stack1/CDKMetadata/Condition", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "Stack1/BootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "Stack1/CheckBootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/index.js b/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/index.js new file mode 100644 index 0000000000000..3e530df5c98e1 --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/index.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const s3 = require("aws-cdk-lib/aws-s3"); +const core = require("aws-cdk-lib/core"); +exports.default = async () => { + const app = new core.App(); + const stack = new core.Stack(app, 'Stack1'); + new s3.Bucket(stack, 'MyBucket'); + return app.synth(); +}; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLHlDQUF5QztBQUN6Qyx5Q0FBeUM7QUFFekMsa0JBQWUsS0FBSyxJQUFJLEVBQUU7SUFDeEIsTUFBTSxHQUFHLEdBQUcsSUFBSSxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7SUFDM0IsTUFBTSxLQUFLLEdBQUcsSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRSxRQUFRLENBQUMsQ0FBQztJQUM1QyxJQUFJLEVBQUUsQ0FBQyxNQUFNLENBQUMsS0FBSyxFQUFFLFVBQVUsQ0FBQyxDQUFDO0lBQ2pDLE9BQU8sR0FBRyxDQUFDLEtBQUssRUFBUyxDQUFDO0FBQzVCLENBQUMsQ0FBQyJ9 \ No newline at end of file diff --git a/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/index.ts b/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/index.ts new file mode 100644 index 0000000000000..05f7ec0ad6bfc --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_fixtures/stack-with-bucket/index.ts @@ -0,0 +1,9 @@ +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as core from 'aws-cdk-lib/core'; + +export default async () => { + const app = new core.App(); + const stack = new core.Stack(app, 'Stack1'); + new s3.Bucket(stack, 'MyBucket'); + return app.synth() as any; +}; diff --git a/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/app.js b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/app.js new file mode 100644 index 0000000000000..9272f9609083a --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/app.js @@ -0,0 +1,7 @@ +import * as cdk from 'aws-cdk-lib/core'; + +const app = new cdk.App(); +new cdk.Stack(app, 'Stack1'); +new cdk.Stack(app, 'Stack2'); + +app.synth(); diff --git a/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.json b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.json new file mode 100644 index 0000000000000..7f138728ebb7d --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "node app.js" +} diff --git a/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/Stack1.assets.json b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/Stack1.assets.json new file mode 100644 index 0000000000000..d0ebcbee59ef5 --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/Stack1.assets.json @@ -0,0 +1,19 @@ +{ + "version": "39.0.0", + "files": { + "8626acb446383dff0f822db8ebf01f14b26ac8317715abc64b02cf2d063e7c21": { + "source": { + "path": "Stack1.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "8626acb446383dff0f822db8ebf01f14b26ac8317715abc64b02cf2d063e7c21.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/Stack1.template.json b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/Stack1.template.json new file mode 100644 index 0000000000000..075363de4a822 --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/Stack1.template.json @@ -0,0 +1,314 @@ +{ + "Resources": { + "CDKMetadata": { + "Type": "AWS::CDK::Metadata", + "Properties": { + "Analytics": "v2:deflate64:H4sIAAAAAAAA/8vLT0nVyyrWLzMy0DM01jNUzCrOzNQtKs0rycxN1QuC0ADPiCvwJQAAAA==" + }, + "Metadata": { + "aws:cdk:path": "Stack1/CDKMetadata/Default" + }, + "Condition": "CDKMetadataAvailable" + } + }, + "Conditions": { + "CDKMetadataAvailable": { + "Fn::Or": [ + { + "Fn::Or": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "af-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-east-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-northeast-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-northeast-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-northeast-3" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-south-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-3" + ] + } + ] + }, + { + "Fn::Or": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-4" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ca-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ca-west-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "cn-north-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "cn-northwest-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-central-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-north-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-south-2" + ] + } + ] + }, + { + "Fn::Or": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-west-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-west-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-west-3" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "il-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "me-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "me-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "sa-east-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-east-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-east-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-west-1" + ] + } + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-west-2" + ] + } + ] + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/Stack2.assets.json b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/Stack2.assets.json new file mode 100644 index 0000000000000..d1f936683fa85 --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/Stack2.assets.json @@ -0,0 +1,19 @@ +{ + "version": "39.0.0", + "files": { + "c7fcbf00a6a6fe490919c378f5562688fd47e1cdcaa01dee160df1c098ef11e2": { + "source": { + "path": "Stack2.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "c7fcbf00a6a6fe490919c378f5562688fd47e1cdcaa01dee160df1c098ef11e2.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/Stack2.template.json b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/Stack2.template.json new file mode 100644 index 0000000000000..90545579d7afb --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/Stack2.template.json @@ -0,0 +1,314 @@ +{ + "Resources": { + "CDKMetadata": { + "Type": "AWS::CDK::Metadata", + "Properties": { + "Analytics": "v2:deflate64:H4sIAAAAAAAA/8vLT0nVyyrWLzMy0DM01jNUzCrOzNQtKs0rycxN1QuC0ADPiCvwJQAAAA==" + }, + "Metadata": { + "aws:cdk:path": "Stack2/CDKMetadata/Default" + }, + "Condition": "CDKMetadataAvailable" + } + }, + "Conditions": { + "CDKMetadataAvailable": { + "Fn::Or": [ + { + "Fn::Or": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "af-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-east-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-northeast-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-northeast-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-northeast-3" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-south-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-3" + ] + } + ] + }, + { + "Fn::Or": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-4" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ca-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ca-west-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "cn-north-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "cn-northwest-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-central-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-north-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-south-2" + ] + } + ] + }, + { + "Fn::Or": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-west-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-west-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-west-3" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "il-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "me-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "me-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "sa-east-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-east-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-east-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-west-1" + ] + } + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-west-2" + ] + } + ] + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/cdk.out b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/cdk.out new file mode 100644 index 0000000000000..91e1a8b9901d5 --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/cdk.out @@ -0,0 +1 @@ +{"version":"39.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/manifest.json b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/manifest.json new file mode 100644 index 0000000000000..dd94d740a30da --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/manifest.json @@ -0,0 +1,131 @@ +{ + "version": "39.0.0", + "artifacts": { + "Stack1.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "Stack1.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "Stack1": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "Stack1.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/8626acb446383dff0f822db8ebf01f14b26ac8317715abc64b02cf2d063e7c21.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "Stack1.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "Stack1.assets" + ], + "metadata": { + "/Stack1/CDKMetadata/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "CDKMetadata" + } + ], + "/Stack1/CDKMetadata/Condition": [ + { + "type": "aws:cdk:logicalId", + "data": "CDKMetadataAvailable" + } + ], + "/Stack1/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/Stack1/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "Stack1" + }, + "Stack2.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "Stack2.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "Stack2": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "Stack2.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/c7fcbf00a6a6fe490919c378f5562688fd47e1cdcaa01dee160df1c098ef11e2.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "Stack2.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "Stack2.assets" + ], + "metadata": { + "/Stack2/CDKMetadata/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "CDKMetadata" + } + ], + "/Stack2/CDKMetadata/Condition": [ + { + "type": "aws:cdk:logicalId", + "data": "CDKMetadataAvailable" + } + ], + "/Stack2/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/Stack2/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "Stack2" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/tree.json b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/tree.json new file mode 100644 index 0000000000000..3a70240387bf1 --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/cdk.out/tree.json @@ -0,0 +1,125 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Stack1": { + "id": "Stack1", + "path": "Stack1", + "children": { + "CDKMetadata": { + "id": "CDKMetadata", + "path": "Stack1/CDKMetadata", + "children": { + "Default": { + "id": "Default", + "path": "Stack1/CDKMetadata/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + }, + "Condition": { + "id": "Condition", + "path": "Stack1/CDKMetadata/Condition", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "Stack1/BootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "Stack1/CheckBootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + }, + "Stack2": { + "id": "Stack2", + "path": "Stack2", + "children": { + "CDKMetadata": { + "id": "CDKMetadata", + "path": "Stack2/CDKMetadata", + "children": { + "Default": { + "id": "Default", + "path": "Stack2/CDKMetadata/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + }, + "Condition": { + "id": "Condition", + "path": "Stack2/CDKMetadata/Condition", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "Stack2/BootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "Stack2/CheckBootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/index.js b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/index.js new file mode 100644 index 0000000000000..547c2827c2181 --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/index.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const core = require("aws-cdk-lib/core"); +exports.default = async () => { + const app = new core.App(); + new core.Stack(app, 'Stack1'); + new core.Stack(app, 'Stack2'); + // @todo fix api + return app.synth(); +}; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLHlDQUF5QztBQUV6QyxrQkFBZSxLQUFLLElBQUksRUFBRTtJQUN4QixNQUFNLEdBQUcsR0FBRyxJQUFJLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztJQUMzQixJQUFJLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxFQUFFLFFBQVEsQ0FBQyxDQUFDO0lBQzlCLElBQUksSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFFOUIsZ0JBQWdCO0lBQ2hCLE9BQU8sR0FBRyxDQUFDLEtBQUssRUFBUyxDQUFDO0FBQzVCLENBQUMsQ0FBQyJ9 \ No newline at end of file diff --git a/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/index.ts b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/index.ts new file mode 100644 index 0000000000000..5502e0b07d22c --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_fixtures/two-empty-stacks/index.ts @@ -0,0 +1,10 @@ +import * as core from 'aws-cdk-lib/core'; + +export default async () => { + const app = new core.App(); + new core.Stack(app, 'Stack1'); + new core.Stack(app, 'Stack2'); + + // @todo fix api + return app.synth() as any; +}; diff --git a/packages/@aws-cdk/toolkit/test/_helpers/index.ts b/packages/@aws-cdk/toolkit/test/_helpers/index.ts new file mode 100644 index 0000000000000..c619ee3c7919f --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_helpers/index.ts @@ -0,0 +1,37 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { Toolkit } from '../../lib'; +import { determineOutputDirectory } from '../../lib/api/cloud-assembly/private'; + +export * from './test-io-host'; + +function fixturePath(...parts: string[]): string { + return path.normalize(path.join(__dirname, '..', '_fixtures', ...parts)); +} + +export async function appFixture(toolkit: Toolkit, name: string) { + const appPath = fixturePath(name, 'app.js'); + if (!fs.existsSync(appPath)) { + throw new Error(`App Fixture ${name} does not exist in ${appPath}`); + } + const app = `cat ${appPath} | node --input-type=module`; + return toolkit.fromCdkApp(app, { + outdir: determineOutputDirectory(), + }); +} + +export function builderFixture(toolkit: Toolkit, name: string) { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const builder = require(path.join(__dirname, '..', '_fixtures', name)).default; + return toolkit.fromAssemblyBuilder(builder, { + outdir: determineOutputDirectory(), + }); +} + +export function cdkOutFixture(toolkit: Toolkit, name: string) { + const outdir = path.join(__dirname, '..', '_fixtures', name, 'cdk.out'); + if (!fs.existsSync(outdir)) { + throw new Error(`Assembly Dir Fixture ${name} does not exist in ${outdir}`); + } + return toolkit.fromAssemblyDirectory(outdir); +} diff --git a/packages/@aws-cdk/toolkit/test/_helpers/test-io-host.ts b/packages/@aws-cdk/toolkit/test/_helpers/test-io-host.ts new file mode 100644 index 0000000000000..d8d0dd1641a3d --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/_helpers/test-io-host.ts @@ -0,0 +1,24 @@ +import { IIoHost, IoMessage, IoMessageLevel, IoRequest, isMessageRelevantForLevel } from '../../lib'; + +/** + * A test implementation of IIoHost that does nothing but can by spied on. + * Optional set a level to filter out all irrelevant messages. + */ +export class TestIoHost implements IIoHost { + public readonly spy: jest.Mock; + + constructor(public level: IoMessageLevel = 'info') { + this.spy = jest.fn(); + } + + public async notify(msg: IoMessage): Promise { + if (isMessageRelevantForLevel(msg, this.level)) { + this.spy(msg); + } + } + + public async requestResponse(msg: IoRequest): Promise { + await this.notify(msg); + return msg.defaultResponse; + } +} diff --git a/packages/@aws-cdk/toolkit/test/actions/synth.test.ts b/packages/@aws-cdk/toolkit/test/actions/synth.test.ts new file mode 100644 index 0000000000000..3b64136b1d397 --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/actions/synth.test.ts @@ -0,0 +1,76 @@ +import { Toolkit } from '../../lib/toolkit'; +import { appFixture, builderFixture, TestIoHost } from '../_helpers'; + +const ioHost = new TestIoHost(); +const toolkit = new Toolkit({ ioHost }); + +beforeEach(() => { + ioHost.spy.mockClear(); +}); + +describe('synth', () => { + test('synth from builder', async () => { + // WHEN + const cx = await builderFixture(toolkit, 'two-empty-stacks'); + await toolkit.synth(cx); + + // THEN + expect(ioHost.spy).toHaveBeenCalledWith(expect.objectContaining({ + action: 'synth', + level: 'info', + message: expect.stringContaining('Successfully synthesized'), + })); + }); + + test('synth from app', async () => { + // WHEN + const cx = await appFixture(toolkit, 'two-empty-stacks'); + await toolkit.synth(cx); + + // THEN + expect(ioHost.spy).toHaveBeenCalledWith(expect.objectContaining({ + action: 'synth', + level: 'info', + message: expect.stringContaining('Successfully synthesized'), + })); + }); + + test('single stack returns the stack', async () => { + // WHEN + const cx = await builderFixture(toolkit, 'stack-with-bucket'); + await toolkit.synth(cx); + + // THEN + expect(ioHost.spy).toHaveBeenCalledWith(expect.objectContaining({ + action: 'synth', + level: 'info', + code: 'CDK_TOOLKIT_I0001', + message: expect.stringContaining('Successfully synthesized'), + data: expect.objectContaining({ + stacksCount: 1, + stack: expect.objectContaining({ + hierarchicalId: 'Stack1', + stackName: 'Stack1', + stringifiedJson: expect.not.stringContaining('CheckBootstrapVersion'), + }), + }), + })); + }); + + test('multiple stacks returns the ids', async () => { + // WHEN + await toolkit.synth(await appFixture(toolkit, 'two-empty-stacks')); + + // THEN + expect(ioHost.spy).toHaveBeenCalledWith(expect.objectContaining({ + action: 'synth', + level: 'info', + code: 'CDK_TOOLKIT_I0002', + message: expect.stringContaining('Successfully synthesized'), + data: expect.objectContaining({ + stacksCount: 2, + stackIds: ['Stack1', 'Stack2'], + }), + })); + }); +}); diff --git a/packages/@aws-cdk/toolkit/test/api/cloud-assembly/source-builder.test.ts b/packages/@aws-cdk/toolkit/test/api/cloud-assembly/source-builder.test.ts new file mode 100644 index 0000000000000..9e5ec588963c4 --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/api/cloud-assembly/source-builder.test.ts @@ -0,0 +1,45 @@ +import { Toolkit } from '../../../lib/toolkit'; +import { appFixture, builderFixture, cdkOutFixture, TestIoHost } from '../../_helpers'; + +const ioHost = new TestIoHost(); +const toolkit = new Toolkit({ ioHost }); + +beforeEach(() => { + ioHost.spy.mockClear(); +}); + +describe('fromAssemblyBuilder', () => { + test('defaults', async () => { + // WHEN + const cx = await builderFixture(toolkit, 'two-empty-stacks'); + const cached = await toolkit.synth(cx); + const assembly = await cached.produce(); + + // THEN + expect(assembly.stacksRecursively.map(s => s.hierarchicalId)).toEqual(['Stack1', 'Stack2']); + }); +}); + +describe('fromAssemblyDirectory', () => { + test('defaults', async () => { + // WHEN + const cx = await cdkOutFixture(toolkit, 'two-empty-stacks'); + const cached = await toolkit.synth(cx); + const assembly = await cached.produce(); + + // THEN + expect(assembly.stacksRecursively.map(s => s.hierarchicalId)).toEqual(['Stack1', 'Stack2']); + }); +}); + +describe('fromCdkApp', () => { + test('defaults', async () => { + // WHEN + const cx = await appFixture(toolkit, 'two-empty-stacks'); + const cached = await toolkit.synth(cx); + const assembly = await cached.produce(); + + // THEN + expect(assembly.stacksRecursively.map(s => s.hierarchicalId)).toEqual(['Stack1', 'Stack2']); + }); +}); diff --git a/packages/@aws-cdk/toolkit/test/api/io/io-message.test.ts b/packages/@aws-cdk/toolkit/test/api/io/io-message.test.ts new file mode 100644 index 0000000000000..9663cf2391c2f --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/api/io/io-message.test.ts @@ -0,0 +1,13 @@ +import { isMessageRelevantForLevel } from '../../../lib/api/io/io-message'; + +describe('IoMessageLevel', () => { + test.each` + msgLevel | logLevel | isRelevant + ${'error'} | ${'trace'} | ${true} + ${'info'} | ${'trace'} | ${true} + ${'info'} | ${'warn'} | ${false} + ${'trace'} | ${'error'} | ${false} + `('with msgLevel=$msgLevel and logLevel=$msgLevel, logging should be $shouldLog', async ({ msgLevel, logLevel, isRelevant }) => { + expect(isMessageRelevantForLevel({ level: msgLevel }, logLevel)).toBe(isRelevant); + }); +}); diff --git a/packages/@aws-cdk/toolkit/test/toolkit/toolkit.test.ts b/packages/@aws-cdk/toolkit/test/toolkit/toolkit.test.ts new file mode 100644 index 0000000000000..00a9f2f4fc0af --- /dev/null +++ b/packages/@aws-cdk/toolkit/test/toolkit/toolkit.test.ts @@ -0,0 +1,8 @@ +/** + * NOTE: This test suite should only contain tests for creating the Toolkit and its methods. + * + * - Actions: Tests for each action go into the `test/actions` directory + * - Source Builders: Tests for the Cloud Assembly Source Builders are in `test/api/cloud-assembly/source-builder.test.ts` + */ + +test('test', () => expect(true).toBe(true)); diff --git a/packages/@aws-cdk/toolkit/tsconfig.json b/packages/@aws-cdk/toolkit/tsconfig.json index e5e0cf1849574..c4081f47c5db2 100644 --- a/packages/@aws-cdk/toolkit/tsconfig.json +++ b/packages/@aws-cdk/toolkit/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "esModuleInterop": true, + "esModuleInterop": false, "skipLibCheck": true, "target": "es2022", "allowJs": true, @@ -25,6 +25,7 @@ "references": [ { "path": "../cx-api" }, { "path": "../cloudformation-diff" }, - { "path": "../../aws-cdk" } + { "path": "../../aws-cdk" }, + { "path": "../../aws-cdk-lib" } ] }