diff --git a/packages/aws-cdk/lib/cdk-toolkit.ts b/packages/aws-cdk/lib/cdk-toolkit.ts index 91ef83426d587..b2400366f5331 100644 --- a/packages/aws-cdk/lib/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cdk-toolkit.ts @@ -4,7 +4,9 @@ import * as cxapi from '@aws-cdk/cx-api'; import * as chalk from 'chalk'; import * as chokidar from 'chokidar'; import * as fs from 'fs-extra'; +import { minimatch } from 'minimatch'; import * as promptly from 'promptly'; +import * as semver from 'semver'; import * as uuid from 'uuid'; import { DeploymentMethod } from './api'; import { SdkProvider } from './api/aws-auth'; @@ -29,6 +31,7 @@ import { validateSnsTopicArn } from './util/validate-notification-arn'; import { Concurrency, WorkGraph } from './util/work-graph'; import { WorkGraphBuilder } from './util/work-graph-builder'; import { AssetBuildNode, AssetPublishNode, StackNode } from './util/work-graph-types'; +import { versionNumber } from './version'; import { environmentsFromDescriptors, globEnvironmentsFromStacks, looksLikeGlob } from '../lib/api/cxapp/environments'; export interface CdkToolkitProps { @@ -589,6 +592,9 @@ export class CdkToolkit { stacks = stacks.reversed(); if (!options.force) { + if (stacks.stackArtifacts.length === 0) { + return; + } // eslint-disable-next-line max-len const confirmed = await promptly.confirm(`Are you sure you want to delete: ${chalk.blue(stacks.stackArtifacts.map(s => s.hierarchicalId).join(', '))} (y/n)?`); if (!confirmed) { @@ -882,9 +888,43 @@ export class CdkToolkit { extend: exclusively ? ExtendedStackSelection.None : ExtendedStackSelection.Downstream, defaultBehavior: DefaultSelection.OnlySingle, }); + const selectorWithoutPatterns: StackSelector = { + ...selector, + allTopLevel: true, + patterns: [], + }; + const stacksWithoutPatterns = await assembly.selectStacks(selectorWithoutPatterns, { + extend: exclusively ? ExtendedStackSelection.None : ExtendedStackSelection.Downstream, + defaultBehavior: DefaultSelection.OnlySingle, + }); - // No validation + const patterns = selector.patterns.map(pattern => { + const notExist = !stacks.stackArtifacts.find(stack => + minimatch(stack.hierarchicalId, pattern) || (stack.id === pattern && semver.major(versionNumber()) < 2), + ); + const closelyMatched = notExist ? stacksWithoutPatterns.stackArtifacts.map(stack => { + if (minimatch(stack.hierarchicalId.toLowerCase(), pattern.toLowerCase())) { + return stack.hierarchicalId; + } + if (stack.id.toLowerCase() === pattern.toLowerCase() && semver.major(versionNumber()) < 2) { + return stack.id; + } + return; + }).filter((stack): stack is string => stack !== undefined) : []; + return { + pattern, + notExist, + closelyMatched, + }; + }); + + patterns.forEach(pattern => { + if (pattern.notExist) { + const closelyMatched = pattern.closelyMatched.length > 0 ? ` Do you mean ${pattern.closelyMatched.join(', ')}?` : ''; + warning(`${pattern.pattern} does not exist.${closelyMatched}`); + } + }); return stacks; }