diff --git a/packages/aws-cdk/lib/cli/cdk-toolkit.ts b/packages/aws-cdk/lib/cli/cdk-toolkit.ts index 84065c474..0ca6ebcc0 100644 --- a/packages/aws-cdk/lib/cli/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cli/cdk-toolkit.ts @@ -55,6 +55,7 @@ import { } from '../commands/migrate'; import type { CloudAssembly, CloudExecutable, StackSelector } from '../cxapp'; import { DefaultSelection, environmentsFromDescriptors, globEnvironmentsFromStacks, looksLikeGlob } from '../cxapp'; +import { OBSOLETE_FLAGS } from '../obsolete-flags'; import { deserializeStructure, formatErrorMessage, @@ -195,7 +196,7 @@ export class CdkToolkit { emojis: true, ioHost: this.ioHost, toolkitStackName: this.toolkitStackName, - unstableFeatures: ['refactor'], + unstableFeatures: ['refactor', 'flags'], }); } @@ -1058,6 +1059,8 @@ export class CdkToolkit { ): Promise { const stacks = await this.selectStacksForDiff(stackNames, exclusively, autoValidate); + await displayFlagsMessage(this.toolkit, this.props.cloudExecutable, this.ioHost.asIoHelper()); + // if we have a single stack, print it to STDOUT if (stacks.stackCount === 1) { if (!quiet) { @@ -2108,6 +2111,16 @@ async function askUserConfirmation( } }); } +export async function displayFlagsMessage(toolkit: InternalToolkit, cloudExecutable: CloudExecutable, + ioHelper: IoHelper): Promise { + let numUnconfigured = (await toolkit.flags(cloudExecutable)) + .filter(flag => !OBSOLETE_FLAGS.includes(flag.name)) + .filter(flag => flag.userValue === undefined).length; + + if (numUnconfigured > 0) { + await ioHelper.defaults.info(`You currently have ${numUnconfigured} unconfigured feature flags that may require attention to keep your application up-to-date. Run 'cdk flags' to learn more.`); + } +} /** * Logger for processing stack metadata diff --git a/packages/aws-cdk/lib/cli/cli.ts b/packages/aws-cdk/lib/cli/cli.ts index c1d07f9ad..164588490 100644 --- a/packages/aws-cdk/lib/cli/cli.ts +++ b/packages/aws-cdk/lib/cli/cli.ts @@ -457,7 +457,6 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise { + let ioHost: TestIoHost; + let ioHelper: any; + let mockToolkit: jest.Mocked; + let mockCloudExecutable: any; + + beforeEach(() => { + ioHost = new TestIoHost(); + ioHelper = asIoHelper(ioHost, 'synth'); + mockCloudExecutable = {}; + + mockToolkit = { + flags: jest.fn(), + } as any; + + jest.spyOn(Toolkit.prototype, 'flags').mockImplementation(mockToolkit.flags); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('displays message with correct count of unconfigured flags, filtering out obsolete flags', async () => { + const mockFlagsData = [ + { + name: '@aws-cdk/core:testFlag', + userValue: undefined, + recommendedValue: 'true', + explanation: 'Test flag', + module: 'aws-cdk-lib', + }, + { + name: '@aws-cdk/s3:anotherFlag', + userValue: 'false', + recommendedValue: 'false', + explanation: 'Another test flag', + module: 'aws-cdk-lib', + }, + { + name: '@aws-cdk/core:enableStackNameDuplicates', + userValue: undefined, + recommendedValue: 'true', + explanation: 'Obsolete flag', + module: 'aws-cdk-lib', + }, + ]; + + mockToolkit.flags.mockResolvedValue(mockFlagsData); + + await displayFlagsMessage(mockToolkit as any, mockCloudExecutable, ioHelper); + + expect(mockToolkit.flags).toHaveBeenCalledWith(mockCloudExecutable); + expect(ioHost.notifySpy).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'You currently have 1 unconfigured feature flags that may require attention to keep your application up-to-date. Run \'cdk flags\' to learn more.', + level: 'info', + }), + ); + }); + test('does not display a message when user has no unconfigured flags', async () => { + const mockFlagsData = [ + { + name: '@aws-cdk/s3:anotherFlag', + userValue: 'false', + recommendedValue: 'false', + explanation: 'Another test flag', + module: 'aws-cdk-lib', + }, + ]; + mockToolkit.flags.mockResolvedValue(mockFlagsData); + + await displayFlagsMessage(mockToolkit as any, mockCloudExecutable, ioHelper); + + expect(mockToolkit.flags).toHaveBeenCalledWith(mockCloudExecutable); + expect(ioHost.notifySpy).not.toHaveBeenCalled(); + }); +}); +