@go-to-k/cdkd 0.3.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -380,9 +380,15 @@ cdkd deploy Stack1 Stack2
380
380
  # Deploy all stacks
381
381
  cdkd deploy --all
382
382
 
383
- # Deploy with wildcard
383
+ # Deploy with wildcard (matched against the physical CloudFormation stack name)
384
384
  cdkd deploy 'My*'
385
385
 
386
+ # Deploy stacks under a CDK Stage using the hierarchical path (CDK CLI parity)
387
+ # Patterns containing '/' are routed to the CDK display path; both forms work:
388
+ cdkd deploy 'MyStage/*' # all stacks under MyStage
389
+ cdkd deploy MyStage/Api # specific stack by display path
390
+ cdkd deploy MyStage-Api # same stack by physical CloudFormation name
391
+
386
392
  # Deploy with context values
387
393
  cdkd deploy -c env=staging -c featureFlag=true
388
394
 
package/dist/cli.js CHANGED
@@ -527,6 +527,141 @@ var destroyOptions = [
527
527
  )
528
528
  ];
529
529
 
530
+ // src/utils/live-renderer.ts
531
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
532
+ var FRAME_INTERVAL_MS = 80;
533
+ var ESC = "\x1B[";
534
+ var LiveRenderer = class {
535
+ constructor(stream = process.stdout) {
536
+ this.stream = stream;
537
+ }
538
+ tasks = /* @__PURE__ */ new Map();
539
+ active = false;
540
+ spinnerIndex = 0;
541
+ interval = null;
542
+ linesDrawn = 0;
543
+ cursorHidden = false;
544
+ exitListener = null;
545
+ isActive() {
546
+ return this.active;
547
+ }
548
+ /**
549
+ * Enable the live renderer. No-op if stdout is not a TTY or if
550
+ * `CDKD_NO_LIVE=1`. Returns true if successfully enabled.
551
+ */
552
+ start() {
553
+ if (this.active)
554
+ return true;
555
+ if (!this.stream.isTTY)
556
+ return false;
557
+ if (process.env["CDKD_NO_LIVE"] === "1")
558
+ return false;
559
+ this.active = true;
560
+ this.hideCursor();
561
+ if (!this.exitListener) {
562
+ this.exitListener = () => this.showCursor();
563
+ process.on("exit", this.exitListener);
564
+ }
565
+ this.interval = setInterval(() => this.draw(), FRAME_INTERVAL_MS);
566
+ if (typeof this.interval.unref === "function")
567
+ this.interval.unref();
568
+ return true;
569
+ }
570
+ stop() {
571
+ if (!this.active)
572
+ return;
573
+ if (this.interval) {
574
+ clearInterval(this.interval);
575
+ this.interval = null;
576
+ }
577
+ this.clear();
578
+ this.showCursor();
579
+ if (this.exitListener) {
580
+ process.removeListener("exit", this.exitListener);
581
+ this.exitListener = null;
582
+ }
583
+ this.tasks.clear();
584
+ this.active = false;
585
+ }
586
+ addTask(id, label) {
587
+ this.tasks.set(id, { label, startedAt: Date.now() });
588
+ if (this.active)
589
+ this.draw();
590
+ }
591
+ removeTask(id) {
592
+ if (!this.tasks.delete(id))
593
+ return;
594
+ if (this.active)
595
+ this.draw();
596
+ }
597
+ /**
598
+ * Print content above the live area. Clears the live area, runs the writer,
599
+ * then redraws the live area. When the renderer is inactive, the writer
600
+ * runs directly so callers can use this unconditionally.
601
+ */
602
+ printAbove(write) {
603
+ if (!this.active) {
604
+ write();
605
+ return;
606
+ }
607
+ this.clear();
608
+ write();
609
+ this.draw();
610
+ }
611
+ clear() {
612
+ if (this.linesDrawn === 0)
613
+ return;
614
+ this.stream.write("\r");
615
+ for (let i = 0; i < this.linesDrawn; i++) {
616
+ this.stream.write(`${ESC}1A${ESC}2K`);
617
+ }
618
+ this.linesDrawn = 0;
619
+ }
620
+ draw() {
621
+ if (!this.active)
622
+ return;
623
+ this.clear();
624
+ if (this.tasks.size === 0)
625
+ return;
626
+ const frame = SPINNER_FRAMES[this.spinnerIndex % SPINNER_FRAMES.length];
627
+ this.spinnerIndex++;
628
+ const cols = this.stream.columns ?? 80;
629
+ const lines = [];
630
+ for (const task of this.tasks.values()) {
631
+ const elapsed = ((Date.now() - task.startedAt) / 1e3).toFixed(1);
632
+ const raw = ` ${frame} ${task.label} (${elapsed}s)`;
633
+ lines.push(this.truncate(raw, cols));
634
+ }
635
+ this.stream.write(lines.join("\n") + "\n");
636
+ this.linesDrawn = lines.length;
637
+ }
638
+ truncate(s, maxLen) {
639
+ if (s.length <= maxLen)
640
+ return s;
641
+ if (maxLen <= 1)
642
+ return "\u2026";
643
+ return s.substring(0, maxLen - 1) + "\u2026";
644
+ }
645
+ hideCursor() {
646
+ if (this.cursorHidden)
647
+ return;
648
+ this.stream.write(`${ESC}?25l`);
649
+ this.cursorHidden = true;
650
+ }
651
+ showCursor() {
652
+ if (!this.cursorHidden)
653
+ return;
654
+ this.stream.write(`${ESC}?25h`);
655
+ this.cursorHidden = false;
656
+ }
657
+ };
658
+ var globalRenderer = null;
659
+ function getLiveRenderer() {
660
+ if (!globalRenderer)
661
+ globalRenderer = new LiveRenderer();
662
+ return globalRenderer;
663
+ }
664
+
530
665
  // src/utils/logger.ts
531
666
  var colors = {
532
667
  reset: "\x1B[0m",
@@ -583,22 +718,26 @@ var ConsoleLogger = class {
583
718
  }
584
719
  debug(message, ...args) {
585
720
  if (this.shouldLog("debug")) {
586
- console.debug(this.formatMessage("debug", message, ...args));
721
+ const formatted = this.formatMessage("debug", message, ...args);
722
+ getLiveRenderer().printAbove(() => console.debug(formatted));
587
723
  }
588
724
  }
589
725
  info(message, ...args) {
590
726
  if (this.shouldLog("info")) {
591
- console.info(this.formatMessage("info", message, ...args));
727
+ const formatted = this.formatMessage("info", message, ...args);
728
+ getLiveRenderer().printAbove(() => console.info(formatted));
592
729
  }
593
730
  }
594
731
  warn(message, ...args) {
595
732
  if (this.shouldLog("warn")) {
596
- console.warn(this.formatMessage("warn", message, ...args));
733
+ const formatted = this.formatMessage("warn", message, ...args);
734
+ getLiveRenderer().printAbove(() => console.warn(formatted));
597
735
  }
598
736
  }
599
737
  error(message, ...args) {
600
738
  if (this.shouldLog("error")) {
601
- console.error(this.formatMessage("error", message, ...args));
739
+ const formatted = this.formatMessage("error", message, ...args);
740
+ getLiveRenderer().printAbove(() => console.error(formatted));
602
741
  }
603
742
  }
604
743
  /**
@@ -1243,6 +1382,7 @@ var AssemblyReader = class {
1243
1382
  }
1244
1383
  return {
1245
1384
  stackName,
1385
+ displayName: artifact.displayName ?? stackName,
1246
1386
  artifactId,
1247
1387
  template,
1248
1388
  assetManifestPath,
@@ -11432,10 +11572,14 @@ var LambdaFunctionProvider = class {
11432
11572
  }
11433
11573
  /**
11434
11574
  * List Lambda-managed ENIs for the given function, paginating through
11435
- * DescribeNetworkInterfaces and filtering on Description substring.
11575
+ * DescribeNetworkInterfaces and filtering on Description.
11436
11576
  *
11437
- * Server-side filter (`description`) does not support wildcards on this
11438
- * API, so we narrow with `requester-id` + match Description client-side.
11577
+ * We filter directly on `description=AWS Lambda VPC ENI-*` (the EC2 API
11578
+ * supports `*` wildcards on this filter same approach as delstack). An
11579
+ * earlier attempt narrowed with `requester-id=*:awslambda_*`, but real
11580
+ * Lambda hyperplane ENIs carry a RequesterId of the form
11581
+ * `AROAXXX...:<account-id>` (no literal "awslambda" substring), so that
11582
+ * filter matched nothing and the cleanup loop quietly listed zero ENIs.
11439
11583
  */
11440
11584
  async listLambdaEnis(functionName) {
11441
11585
  const enis = [];
@@ -11444,10 +11588,7 @@ var LambdaFunctionProvider = class {
11444
11588
  do {
11445
11589
  const resp = await this.ec2Client.send(
11446
11590
  new DescribeNetworkInterfacesCommand({
11447
- Filters: [
11448
- // Lambda hyperplane ENIs are owned by the Lambda service principal.
11449
- { Name: "requester-id", Values: ["*:awslambda_*"] }
11450
- ],
11591
+ Filters: [{ Name: "description", Values: [`${descriptionPrefix}*`] }],
11451
11592
  NextToken: nextToken
11452
11593
  })
11453
11594
  );
@@ -14407,7 +14548,10 @@ var EC2Provider = class {
14407
14548
  new DescribeNetworkInterfacesCommand2({
14408
14549
  Filters: [
14409
14550
  { Name: "subnet-id", Values: [subnetId] },
14410
- { Name: "requester-id", Values: ["*:awslambda_*"] }
14551
+ // `description` filter is the only reliable way to find Lambda
14552
+ // hyperplane ENIs — `requester-id` does not actually contain the
14553
+ // string "awslambda" (it is an AROA principal id).
14554
+ { Name: "description", Values: ["AWS Lambda VPC ENI-*"] }
14411
14555
  ]
14412
14556
  })
14413
14557
  );
@@ -14965,7 +15109,9 @@ var EC2Provider = class {
14965
15109
  new DescribeNetworkInterfacesCommand2({
14966
15110
  Filters: [
14967
15111
  { Name: "group-id", Values: [groupId] },
14968
- { Name: "requester-id", Values: ["*:awslambda_*"] }
15112
+ // See cleanupSubnetLambdaEnis: requester-id does not contain
15113
+ // "awslambda" — filter on description instead.
15114
+ { Name: "description", Values: ["AWS Lambda VPC ENI-*"] }
14969
15115
  ]
14970
15116
  })
14971
15117
  );
@@ -25868,11 +26014,15 @@ var DeployEngine = class {
25868
26014
  this.logger.debug(`Starting deployment for stack: ${stackName}`);
25869
26015
  setCurrentStackName(stackName);
25870
26016
  await this.lockManager.acquireLockWithRetry(stackName, void 0, "deploy");
26017
+ const renderer = getLiveRenderer();
26018
+ renderer.start();
25871
26019
  this.interrupted = false;
25872
26020
  const sigintHandler = () => {
25873
- process.stderr.write(
25874
- "\nInterrupted \u2014 saving partial state after current operations complete...\n"
25875
- );
26021
+ renderer.printAbove(() => {
26022
+ process.stderr.write(
26023
+ "\nInterrupted \u2014 saving partial state after current operations complete...\n"
26024
+ );
26025
+ });
25876
26026
  this.interrupted = true;
25877
26027
  };
25878
26028
  process.on("SIGINT", sigintHandler);
@@ -25987,6 +26137,7 @@ var DeployEngine = class {
25987
26137
  durationMs
25988
26138
  };
25989
26139
  } finally {
26140
+ renderer.stop();
25990
26141
  process.removeListener("SIGINT", sigintHandler);
25991
26142
  try {
25992
26143
  await this.lockManager.releaseLock(stackName);
@@ -26415,6 +26566,10 @@ var DeployEngine = class {
26415
26566
  async provisionResource(logicalId, change, stateResources, stackName, template, parameterValues, conditions, counts, progress) {
26416
26567
  const resourceType = change.resourceType;
26417
26568
  const provider = this.providerRegistry.getProvider(resourceType);
26569
+ const renderer = getLiveRenderer();
26570
+ const needsReplacement = change.changeType === "UPDATE" && (change.propertyChanges?.some((pc) => pc.requiresReplacement) ?? false);
26571
+ const verb = change.changeType === "CREATE" ? "Creating" : change.changeType === "DELETE" ? "Deleting" : needsReplacement ? "Replacing" : "Updating";
26572
+ renderer.addTask(logicalId, `${verb} ${logicalId} (${resourceType})`);
26418
26573
  try {
26419
26574
  switch (change.changeType) {
26420
26575
  case "CREATE": {
@@ -26446,6 +26601,7 @@ var DeployEngine = class {
26446
26601
  if (progress)
26447
26602
  progress.current++;
26448
26603
  const createPrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
26604
+ renderer.removeTask(logicalId);
26449
26605
  this.logger.info(`${createPrefix}\u2705 ${logicalId} (${resourceType}) created`);
26450
26606
  break;
26451
26607
  }
@@ -26473,9 +26629,9 @@ var DeployEngine = class {
26473
26629
  counts.skipped++;
26474
26630
  break;
26475
26631
  }
26476
- const needsReplacement = change.propertyChanges?.some((pc) => pc.requiresReplacement);
26632
+ const needsReplacement2 = change.propertyChanges?.some((pc) => pc.requiresReplacement);
26477
26633
  const dependencies = this.extractAllDependencies(template, logicalId);
26478
- if (needsReplacement) {
26634
+ if (needsReplacement2) {
26479
26635
  const replacedProps = change.propertyChanges?.filter((pc) => pc.requiresReplacement).map((pc) => pc.path).join(", ");
26480
26636
  this.logger.info(
26481
26637
  `Replacing ${logicalId} (${resourceType}) - immutable properties changed: ${replacedProps}`
@@ -26519,6 +26675,7 @@ var DeployEngine = class {
26519
26675
  if (progress)
26520
26676
  progress.current++;
26521
26677
  const replacePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
26678
+ renderer.removeTask(logicalId);
26522
26679
  this.logger.info(`${replacePrefix}\u2705 ${logicalId} (${resourceType}) replaced`);
26523
26680
  } else {
26524
26681
  this.logger.debug(`Updating ${logicalId} (${resourceType})`);
@@ -26594,6 +26751,7 @@ var DeployEngine = class {
26594
26751
  if (progress)
26595
26752
  progress.current++;
26596
26753
  const updatePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
26754
+ renderer.removeTask(logicalId);
26597
26755
  this.logger.info(`${updatePrefix}\u2705 ${logicalId} (${resourceType}) updated`);
26598
26756
  }
26599
26757
  break;
@@ -26639,11 +26797,13 @@ var DeployEngine = class {
26639
26797
  if (progress)
26640
26798
  progress.current++;
26641
26799
  const deletePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
26800
+ renderer.removeTask(logicalId);
26642
26801
  this.logger.info(`${deletePrefix}\u2705 ${logicalId} (${resourceType}) deleted`);
26643
26802
  break;
26644
26803
  }
26645
26804
  }
26646
26805
  } catch (error) {
26806
+ renderer.removeTask(logicalId);
26647
26807
  const message = error instanceof Error ? error.message : String(error);
26648
26808
  this.logger.error(`Failed to ${change.changeType.toLowerCase()} ${logicalId}: ${message}`);
26649
26809
  throw new ProvisioningError(
@@ -26653,6 +26813,8 @@ var DeployEngine = class {
26653
26813
  stateResources[logicalId]?.physicalId,
26654
26814
  error instanceof Error ? error : void 0
26655
26815
  );
26816
+ } finally {
26817
+ renderer.removeTask(logicalId);
26656
26818
  }
26657
26819
  }
26658
26820
  /**
@@ -26916,10 +27078,43 @@ var DeployEngine = class {
26916
27078
 
26917
27079
  // src/cli/commands/deploy.ts
26918
27080
  init_aws_clients();
27081
+
27082
+ // src/cli/stack-matcher.ts
27083
+ function matchStacks(stacks, patterns) {
27084
+ if (patterns.length === 0)
27085
+ return [];
27086
+ const seen = /* @__PURE__ */ new Set();
27087
+ const result = [];
27088
+ for (const stack of stacks) {
27089
+ const matched = patterns.some((pattern) => stackMatchesPattern(stack, pattern));
27090
+ if (matched && !seen.has(stack.stackName)) {
27091
+ seen.add(stack.stackName);
27092
+ result.push(stack);
27093
+ }
27094
+ }
27095
+ return result;
27096
+ }
27097
+ function describeStack(stack) {
27098
+ if (stack.displayName && stack.displayName !== stack.stackName) {
27099
+ return `${stack.stackName} (${stack.displayName})`;
27100
+ }
27101
+ return stack.stackName;
27102
+ }
27103
+ function stackMatchesPattern(stack, pattern) {
27104
+ const target = pattern.includes("/") ? stack.displayName ?? stack.stackName : stack.stackName;
27105
+ if (pattern.includes("*")) {
27106
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
27107
+ return regex.test(target);
27108
+ }
27109
+ return target === pattern;
27110
+ }
27111
+
27112
+ // src/cli/commands/deploy.ts
26919
27113
  async function deployCommand(stacks, options) {
26920
27114
  const logger = getLogger();
26921
27115
  if (options.verbose) {
26922
27116
  logger.setLevel("debug");
27117
+ process.env["CDKD_NO_LIVE"] = "1";
26923
27118
  }
26924
27119
  if (!options.wait) {
26925
27120
  process.env["CDKD_NO_WAIT"] = "true";
@@ -26977,21 +27172,17 @@ async function deployCommand(stacks, options) {
26977
27172
  if (options.all) {
26978
27173
  targetStacks = allStacks;
26979
27174
  } else if (stackPatterns.length > 0) {
26980
- targetStacks = allStacks.filter(
26981
- (s) => stackPatterns.some(
26982
- (pattern) => pattern.includes("*") ? new RegExp("^" + pattern.replace(/\*/g, ".*") + "$").test(s.stackName) : s.stackName === pattern
26983
- )
26984
- );
27175
+ targetStacks = matchStacks(allStacks, stackPatterns);
26985
27176
  } else if (allStacks.length === 1) {
26986
27177
  targetStacks = allStacks;
26987
27178
  } else {
26988
27179
  throw new Error(
26989
- `Multiple stacks found: ${allStacks.map((s) => s.stackName).join(", ")}. Specify stack name(s) or use --all`
27180
+ `Multiple stacks found: ${allStacks.map(describeStack).join(", ")}. Specify stack name(s) or use --all`
26990
27181
  );
26991
27182
  }
26992
27183
  if (targetStacks.length === 0) {
26993
27184
  throw new Error(
26994
- stackPatterns.length > 0 ? `No stacks matching ${stackPatterns.join(", ")} found in assembly. Available: ${allStacks.map((s) => s.stackName).join(", ")}` : "No stacks found in assembly"
27185
+ stackPatterns.length > 0 ? `No stacks matching ${stackPatterns.join(", ")} found in assembly. Available: ${allStacks.map(describeStack).join(", ")}` : "No stacks found in assembly"
26995
27186
  );
26996
27187
  }
26997
27188
  if (!options.exclusively) {
@@ -27152,7 +27343,10 @@ Deploying stack: ${stackInfo.stackName}${stackRegion !== baseRegion ? ` (region:
27152
27343
  }
27153
27344
  }
27154
27345
  function createDeployCommand() {
27155
- const cmd = new Command3("deploy").description("Deploy CDK app using SDK/Cloud Control API").argument("[stacks...]", "Stack name(s) to deploy (supports wildcards)").option("--all", "Deploy all stacks", false).action(withErrorHandling(deployCommand));
27346
+ const cmd = new Command3("deploy").description("Deploy CDK app using SDK/Cloud Control API").argument(
27347
+ "[stacks...]",
27348
+ "Stack name(s) to deploy. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards (e.g. 'MyStage/*')."
27349
+ ).option("--all", "Deploy all stacks", false).action(withErrorHandling(deployCommand));
27156
27350
  [
27157
27351
  ...commonOptions,
27158
27352
  ...appOptions,
@@ -27263,21 +27457,17 @@ async function diffCommand(stacks, options) {
27263
27457
  if (options.all) {
27264
27458
  targetStacks = allStacks;
27265
27459
  } else if (stackPatterns.length > 0) {
27266
- targetStacks = allStacks.filter(
27267
- (s) => stackPatterns.some(
27268
- (pattern) => pattern.includes("*") ? new RegExp("^" + pattern.replace(/\*/g, ".*") + "$").test(s.stackName) : s.stackName === pattern
27269
- )
27270
- );
27460
+ targetStacks = matchStacks(allStacks, stackPatterns);
27271
27461
  } else if (allStacks.length === 1) {
27272
27462
  targetStacks = allStacks;
27273
27463
  } else {
27274
27464
  throw new Error(
27275
- `Multiple stacks found: ${allStacks.map((s) => s.stackName).join(", ")}. Specify stack name(s) or use --all`
27465
+ `Multiple stacks found: ${allStacks.map(describeStack).join(", ")}. Specify stack name(s) or use --all`
27276
27466
  );
27277
27467
  }
27278
27468
  if (targetStacks.length === 0) {
27279
27469
  throw new Error(
27280
- stackPatterns.length > 0 ? `No stacks matching ${stackPatterns.join(", ")} found in assembly` : "No stacks found in assembly"
27470
+ stackPatterns.length > 0 ? `No stacks matching ${stackPatterns.join(", ")} found in assembly. Available: ${allStacks.map(describeStack).join(", ")}` : "No stacks found in assembly"
27281
27471
  );
27282
27472
  }
27283
27473
  const stateConfig = {
@@ -27367,7 +27557,10 @@ ${createCount} to create, ${updateCount} to update, ${deleteCount} to delete`);
27367
27557
  }
27368
27558
  }
27369
27559
  function createDiffCommand() {
27370
- const cmd = new Command4("diff").description("Show difference between current state and desired state").argument("[stacks...]", "Stack name(s) to diff (supports wildcards)").option("--all", "Diff all stacks", false).action(withErrorHandling(diffCommand));
27560
+ const cmd = new Command4("diff").description("Show difference between current state and desired state").argument(
27561
+ "[stacks...]",
27562
+ "Stack name(s) to diff. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards (e.g. 'MyStage/*')."
27563
+ ).option("--all", "Diff all stacks", false).action(withErrorHandling(diffCommand));
27371
27564
  [...commonOptions, ...appOptions, ...stateOptions, ...stackOptions, ...contextOptions].forEach(
27372
27565
  (opt) => cmd.addOption(opt)
27373
27566
  );
@@ -27382,6 +27575,7 @@ async function destroyCommand(stackArgs, options) {
27382
27575
  const logger = getLogger();
27383
27576
  if (options.verbose) {
27384
27577
  logger.setLevel("debug");
27578
+ process.env["CDKD_NO_LIVE"] = "1";
27385
27579
  }
27386
27580
  const region = options.region || process.env["AWS_REGION"] || "us-east-1";
27387
27581
  const stateBucket = await resolveStateBucketWithDefault(options.stateBucket, region);
@@ -27409,7 +27603,7 @@ async function destroyCommand(stackArgs, options) {
27409
27603
  registerAllProviders(providerRegistry);
27410
27604
  providerRegistry.setCustomResourceResponseBucket(stateBucket);
27411
27605
  const appCmd = options.app || resolveApp();
27412
- let appStackNames = [];
27606
+ let appStacks = [];
27413
27607
  if (appCmd) {
27414
27608
  try {
27415
27609
  const synthesizer = new Synthesizer();
@@ -27419,17 +27613,21 @@ async function destroyCommand(stackArgs, options) {
27419
27613
  output: options.output || "cdk.out",
27420
27614
  ...Object.keys(context).length > 0 && { context }
27421
27615
  });
27422
- appStackNames = result.stacks.map((s) => s.stackName);
27616
+ appStacks = result.stacks.map((s) => ({
27617
+ stackName: s.stackName,
27618
+ displayName: s.displayName
27619
+ }));
27423
27620
  } catch {
27424
27621
  logger.debug("Could not synthesize app, falling back to state-based stack list");
27425
27622
  }
27426
27623
  }
27427
27624
  const allStateStacks = await stateBackend.listStacks();
27428
27625
  let candidateStacks;
27429
- if (appStackNames.length > 0) {
27430
- candidateStacks = appStackNames.filter((name) => allStateStacks.includes(name));
27626
+ if (appStacks.length > 0) {
27627
+ const stateSet = new Set(allStateStacks);
27628
+ candidateStacks = appStacks.filter((s) => stateSet.has(s.stackName));
27431
27629
  } else if (stackArgs.length > 0 || options.stack || options.all) {
27432
- candidateStacks = allStateStacks;
27630
+ candidateStacks = allStateStacks.map((name) => ({ stackName: name }));
27433
27631
  } else {
27434
27632
  throw new Error(
27435
27633
  "Could not determine which stacks belong to this app. Specify stack names explicitly, use --all, or ensure --app / cdk.json is configured."
@@ -27438,21 +27636,17 @@ async function destroyCommand(stackArgs, options) {
27438
27636
  const stackPatterns = stackArgs.length > 0 ? stackArgs : options.stack ? [options.stack] : [];
27439
27637
  let stackNames;
27440
27638
  if (options.all) {
27441
- stackNames = candidateStacks;
27639
+ stackNames = candidateStacks.map((s) => s.stackName);
27442
27640
  } else if (stackPatterns.length > 0) {
27443
- stackNames = candidateStacks.filter(
27444
- (name) => stackPatterns.some(
27445
- (pattern) => pattern.includes("*") ? new RegExp("^" + pattern.replace(/\*/g, ".*") + "$").test(name) : name === pattern
27446
- )
27447
- );
27641
+ stackNames = matchStacks(candidateStacks, stackPatterns).map((s) => s.stackName);
27448
27642
  } else if (candidateStacks.length === 1) {
27449
- stackNames = candidateStacks;
27643
+ stackNames = candidateStacks.map((s) => s.stackName);
27450
27644
  } else if (candidateStacks.length === 0) {
27451
27645
  logger.info("No stacks found in state");
27452
27646
  return;
27453
27647
  } else {
27454
27648
  throw new Error(
27455
- `Multiple stacks found: ${candidateStacks.join(", ")}. Specify stack name(s) or use --all`
27649
+ `Multiple stacks found: ${candidateStacks.map(describeStack).join(", ")}. Specify stack name(s) or use --all`
27456
27650
  );
27457
27651
  }
27458
27652
  if (stackNames.length === 0) {
@@ -27516,6 +27710,8 @@ Are you sure you want to destroy stack "${stackName}" and delete all ${resourceC
27516
27710
  logger.info(`
27517
27711
  Acquiring lock for stack ${stackName}...`);
27518
27712
  await lockManager.acquireLock(stackName, "destroy");
27713
+ const renderer = getLiveRenderer();
27714
+ renderer.start();
27519
27715
  try {
27520
27716
  logger.info("Building dependency graph...");
27521
27717
  const template = {
@@ -27579,6 +27775,7 @@ Acquiring lock for stack ${stackName}...`);
27579
27775
  logger.warn(`Resource ${logicalId} not found in state, skipping`);
27580
27776
  return;
27581
27777
  }
27778
+ renderer.addTask(logicalId, `Deleting ${logicalId} (${resource.resourceType})`);
27582
27779
  try {
27583
27780
  const provider = destroyProviderRegistry.getProvider(resource.resourceType);
27584
27781
  let lastDeleteError;
@@ -27607,9 +27804,11 @@ Acquiring lock for stack ${stackName}...`);
27607
27804
  }
27608
27805
  if (lastDeleteError)
27609
27806
  throw lastDeleteError;
27807
+ renderer.removeTask(logicalId);
27610
27808
  logger.info(` \u2705 ${logicalId} (${resource.resourceType}) deleted`);
27611
27809
  deletedCount++;
27612
27810
  } catch (error) {
27811
+ renderer.removeTask(logicalId);
27613
27812
  const msg = error instanceof Error ? error.message : String(error);
27614
27813
  if (msg.includes("does not exist") || msg.includes("not found") || msg.includes("No policy found") || msg.includes("NoSuchEntity") || msg.includes("NotFoundException")) {
27615
27814
  logger.debug(` ${logicalId} already deleted, removing from state`);
@@ -27618,6 +27817,8 @@ Acquiring lock for stack ${stackName}...`);
27618
27817
  logger.error(` \u2717 Failed to delete ${logicalId}:`, String(error));
27619
27818
  errorCount++;
27620
27819
  }
27820
+ } finally {
27821
+ renderer.removeTask(logicalId);
27621
27822
  }
27622
27823
  });
27623
27824
  await Promise.all(deletePromises);
@@ -27633,6 +27834,7 @@ Acquiring lock for stack ${stackName}...`);
27633
27834
  \u2713 Stack ${stackName} destroyed (${deletedCount} deleted, ${errorCount} errors)`
27634
27835
  );
27635
27836
  } finally {
27837
+ renderer.stop();
27636
27838
  logger.debug("Releasing lock...");
27637
27839
  await lockManager.releaseLock(stackName);
27638
27840
  if (destroyAwsClients) {
@@ -27648,7 +27850,10 @@ Acquiring lock for stack ${stackName}...`);
27648
27850
  }
27649
27851
  }
27650
27852
  function createDestroyCommand() {
27651
- const cmd = new Command5("destroy").description("Destroy all resources in the stack").argument("[stacks...]", "Stack name(s) to destroy (supports wildcards)").option("--all", "Destroy all stacks", false).action(withErrorHandling(destroyCommand));
27853
+ const cmd = new Command5("destroy").description("Destroy all resources in the stack").argument(
27854
+ "[stacks...]",
27855
+ "Stack name(s) to destroy. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards (e.g. 'MyStage/*')."
27856
+ ).option("--all", "Destroy all stacks", false).action(withErrorHandling(destroyCommand));
27652
27857
  [
27653
27858
  ...commonOptions,
27654
27859
  ...appOptions,
@@ -27762,7 +27967,7 @@ function reorderArgs(argv) {
27762
27967
  }
27763
27968
  async function main() {
27764
27969
  const program = new Command8();
27765
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.3.5");
27970
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.4.0");
27766
27971
  program.addCommand(createBootstrapCommand());
27767
27972
  program.addCommand(createSynthCommand());
27768
27973
  program.addCommand(createDeployCommand());