@go-to-k/cdkd 0.3.6 → 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,
@@ -25874,11 +26014,15 @@ var DeployEngine = class {
25874
26014
  this.logger.debug(`Starting deployment for stack: ${stackName}`);
25875
26015
  setCurrentStackName(stackName);
25876
26016
  await this.lockManager.acquireLockWithRetry(stackName, void 0, "deploy");
26017
+ const renderer = getLiveRenderer();
26018
+ renderer.start();
25877
26019
  this.interrupted = false;
25878
26020
  const sigintHandler = () => {
25879
- process.stderr.write(
25880
- "\nInterrupted \u2014 saving partial state after current operations complete...\n"
25881
- );
26021
+ renderer.printAbove(() => {
26022
+ process.stderr.write(
26023
+ "\nInterrupted \u2014 saving partial state after current operations complete...\n"
26024
+ );
26025
+ });
25882
26026
  this.interrupted = true;
25883
26027
  };
25884
26028
  process.on("SIGINT", sigintHandler);
@@ -25993,6 +26137,7 @@ var DeployEngine = class {
25993
26137
  durationMs
25994
26138
  };
25995
26139
  } finally {
26140
+ renderer.stop();
25996
26141
  process.removeListener("SIGINT", sigintHandler);
25997
26142
  try {
25998
26143
  await this.lockManager.releaseLock(stackName);
@@ -26421,6 +26566,10 @@ var DeployEngine = class {
26421
26566
  async provisionResource(logicalId, change, stateResources, stackName, template, parameterValues, conditions, counts, progress) {
26422
26567
  const resourceType = change.resourceType;
26423
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})`);
26424
26573
  try {
26425
26574
  switch (change.changeType) {
26426
26575
  case "CREATE": {
@@ -26452,6 +26601,7 @@ var DeployEngine = class {
26452
26601
  if (progress)
26453
26602
  progress.current++;
26454
26603
  const createPrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
26604
+ renderer.removeTask(logicalId);
26455
26605
  this.logger.info(`${createPrefix}\u2705 ${logicalId} (${resourceType}) created`);
26456
26606
  break;
26457
26607
  }
@@ -26479,9 +26629,9 @@ var DeployEngine = class {
26479
26629
  counts.skipped++;
26480
26630
  break;
26481
26631
  }
26482
- const needsReplacement = change.propertyChanges?.some((pc) => pc.requiresReplacement);
26632
+ const needsReplacement2 = change.propertyChanges?.some((pc) => pc.requiresReplacement);
26483
26633
  const dependencies = this.extractAllDependencies(template, logicalId);
26484
- if (needsReplacement) {
26634
+ if (needsReplacement2) {
26485
26635
  const replacedProps = change.propertyChanges?.filter((pc) => pc.requiresReplacement).map((pc) => pc.path).join(", ");
26486
26636
  this.logger.info(
26487
26637
  `Replacing ${logicalId} (${resourceType}) - immutable properties changed: ${replacedProps}`
@@ -26525,6 +26675,7 @@ var DeployEngine = class {
26525
26675
  if (progress)
26526
26676
  progress.current++;
26527
26677
  const replacePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
26678
+ renderer.removeTask(logicalId);
26528
26679
  this.logger.info(`${replacePrefix}\u2705 ${logicalId} (${resourceType}) replaced`);
26529
26680
  } else {
26530
26681
  this.logger.debug(`Updating ${logicalId} (${resourceType})`);
@@ -26600,6 +26751,7 @@ var DeployEngine = class {
26600
26751
  if (progress)
26601
26752
  progress.current++;
26602
26753
  const updatePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
26754
+ renderer.removeTask(logicalId);
26603
26755
  this.logger.info(`${updatePrefix}\u2705 ${logicalId} (${resourceType}) updated`);
26604
26756
  }
26605
26757
  break;
@@ -26645,11 +26797,13 @@ var DeployEngine = class {
26645
26797
  if (progress)
26646
26798
  progress.current++;
26647
26799
  const deletePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
26800
+ renderer.removeTask(logicalId);
26648
26801
  this.logger.info(`${deletePrefix}\u2705 ${logicalId} (${resourceType}) deleted`);
26649
26802
  break;
26650
26803
  }
26651
26804
  }
26652
26805
  } catch (error) {
26806
+ renderer.removeTask(logicalId);
26653
26807
  const message = error instanceof Error ? error.message : String(error);
26654
26808
  this.logger.error(`Failed to ${change.changeType.toLowerCase()} ${logicalId}: ${message}`);
26655
26809
  throw new ProvisioningError(
@@ -26659,6 +26813,8 @@ var DeployEngine = class {
26659
26813
  stateResources[logicalId]?.physicalId,
26660
26814
  error instanceof Error ? error : void 0
26661
26815
  );
26816
+ } finally {
26817
+ renderer.removeTask(logicalId);
26662
26818
  }
26663
26819
  }
26664
26820
  /**
@@ -26922,10 +27078,43 @@ var DeployEngine = class {
26922
27078
 
26923
27079
  // src/cli/commands/deploy.ts
26924
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
26925
27113
  async function deployCommand(stacks, options) {
26926
27114
  const logger = getLogger();
26927
27115
  if (options.verbose) {
26928
27116
  logger.setLevel("debug");
27117
+ process.env["CDKD_NO_LIVE"] = "1";
26929
27118
  }
26930
27119
  if (!options.wait) {
26931
27120
  process.env["CDKD_NO_WAIT"] = "true";
@@ -26983,21 +27172,17 @@ async function deployCommand(stacks, options) {
26983
27172
  if (options.all) {
26984
27173
  targetStacks = allStacks;
26985
27174
  } else if (stackPatterns.length > 0) {
26986
- targetStacks = allStacks.filter(
26987
- (s) => stackPatterns.some(
26988
- (pattern) => pattern.includes("*") ? new RegExp("^" + pattern.replace(/\*/g, ".*") + "$").test(s.stackName) : s.stackName === pattern
26989
- )
26990
- );
27175
+ targetStacks = matchStacks(allStacks, stackPatterns);
26991
27176
  } else if (allStacks.length === 1) {
26992
27177
  targetStacks = allStacks;
26993
27178
  } else {
26994
27179
  throw new Error(
26995
- `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`
26996
27181
  );
26997
27182
  }
26998
27183
  if (targetStacks.length === 0) {
26999
27184
  throw new Error(
27000
- 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"
27001
27186
  );
27002
27187
  }
27003
27188
  if (!options.exclusively) {
@@ -27158,7 +27343,10 @@ Deploying stack: ${stackInfo.stackName}${stackRegion !== baseRegion ? ` (region:
27158
27343
  }
27159
27344
  }
27160
27345
  function createDeployCommand() {
27161
- 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));
27162
27350
  [
27163
27351
  ...commonOptions,
27164
27352
  ...appOptions,
@@ -27269,21 +27457,17 @@ async function diffCommand(stacks, options) {
27269
27457
  if (options.all) {
27270
27458
  targetStacks = allStacks;
27271
27459
  } else if (stackPatterns.length > 0) {
27272
- targetStacks = allStacks.filter(
27273
- (s) => stackPatterns.some(
27274
- (pattern) => pattern.includes("*") ? new RegExp("^" + pattern.replace(/\*/g, ".*") + "$").test(s.stackName) : s.stackName === pattern
27275
- )
27276
- );
27460
+ targetStacks = matchStacks(allStacks, stackPatterns);
27277
27461
  } else if (allStacks.length === 1) {
27278
27462
  targetStacks = allStacks;
27279
27463
  } else {
27280
27464
  throw new Error(
27281
- `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`
27282
27466
  );
27283
27467
  }
27284
27468
  if (targetStacks.length === 0) {
27285
27469
  throw new Error(
27286
- 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"
27287
27471
  );
27288
27472
  }
27289
27473
  const stateConfig = {
@@ -27373,7 +27557,10 @@ ${createCount} to create, ${updateCount} to update, ${deleteCount} to delete`);
27373
27557
  }
27374
27558
  }
27375
27559
  function createDiffCommand() {
27376
- 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));
27377
27564
  [...commonOptions, ...appOptions, ...stateOptions, ...stackOptions, ...contextOptions].forEach(
27378
27565
  (opt) => cmd.addOption(opt)
27379
27566
  );
@@ -27388,6 +27575,7 @@ async function destroyCommand(stackArgs, options) {
27388
27575
  const logger = getLogger();
27389
27576
  if (options.verbose) {
27390
27577
  logger.setLevel("debug");
27578
+ process.env["CDKD_NO_LIVE"] = "1";
27391
27579
  }
27392
27580
  const region = options.region || process.env["AWS_REGION"] || "us-east-1";
27393
27581
  const stateBucket = await resolveStateBucketWithDefault(options.stateBucket, region);
@@ -27415,7 +27603,7 @@ async function destroyCommand(stackArgs, options) {
27415
27603
  registerAllProviders(providerRegistry);
27416
27604
  providerRegistry.setCustomResourceResponseBucket(stateBucket);
27417
27605
  const appCmd = options.app || resolveApp();
27418
- let appStackNames = [];
27606
+ let appStacks = [];
27419
27607
  if (appCmd) {
27420
27608
  try {
27421
27609
  const synthesizer = new Synthesizer();
@@ -27425,17 +27613,21 @@ async function destroyCommand(stackArgs, options) {
27425
27613
  output: options.output || "cdk.out",
27426
27614
  ...Object.keys(context).length > 0 && { context }
27427
27615
  });
27428
- appStackNames = result.stacks.map((s) => s.stackName);
27616
+ appStacks = result.stacks.map((s) => ({
27617
+ stackName: s.stackName,
27618
+ displayName: s.displayName
27619
+ }));
27429
27620
  } catch {
27430
27621
  logger.debug("Could not synthesize app, falling back to state-based stack list");
27431
27622
  }
27432
27623
  }
27433
27624
  const allStateStacks = await stateBackend.listStacks();
27434
27625
  let candidateStacks;
27435
- if (appStackNames.length > 0) {
27436
- 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));
27437
27629
  } else if (stackArgs.length > 0 || options.stack || options.all) {
27438
- candidateStacks = allStateStacks;
27630
+ candidateStacks = allStateStacks.map((name) => ({ stackName: name }));
27439
27631
  } else {
27440
27632
  throw new Error(
27441
27633
  "Could not determine which stacks belong to this app. Specify stack names explicitly, use --all, or ensure --app / cdk.json is configured."
@@ -27444,21 +27636,17 @@ async function destroyCommand(stackArgs, options) {
27444
27636
  const stackPatterns = stackArgs.length > 0 ? stackArgs : options.stack ? [options.stack] : [];
27445
27637
  let stackNames;
27446
27638
  if (options.all) {
27447
- stackNames = candidateStacks;
27639
+ stackNames = candidateStacks.map((s) => s.stackName);
27448
27640
  } else if (stackPatterns.length > 0) {
27449
- stackNames = candidateStacks.filter(
27450
- (name) => stackPatterns.some(
27451
- (pattern) => pattern.includes("*") ? new RegExp("^" + pattern.replace(/\*/g, ".*") + "$").test(name) : name === pattern
27452
- )
27453
- );
27641
+ stackNames = matchStacks(candidateStacks, stackPatterns).map((s) => s.stackName);
27454
27642
  } else if (candidateStacks.length === 1) {
27455
- stackNames = candidateStacks;
27643
+ stackNames = candidateStacks.map((s) => s.stackName);
27456
27644
  } else if (candidateStacks.length === 0) {
27457
27645
  logger.info("No stacks found in state");
27458
27646
  return;
27459
27647
  } else {
27460
27648
  throw new Error(
27461
- `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`
27462
27650
  );
27463
27651
  }
27464
27652
  if (stackNames.length === 0) {
@@ -27522,6 +27710,8 @@ Are you sure you want to destroy stack "${stackName}" and delete all ${resourceC
27522
27710
  logger.info(`
27523
27711
  Acquiring lock for stack ${stackName}...`);
27524
27712
  await lockManager.acquireLock(stackName, "destroy");
27713
+ const renderer = getLiveRenderer();
27714
+ renderer.start();
27525
27715
  try {
27526
27716
  logger.info("Building dependency graph...");
27527
27717
  const template = {
@@ -27585,6 +27775,7 @@ Acquiring lock for stack ${stackName}...`);
27585
27775
  logger.warn(`Resource ${logicalId} not found in state, skipping`);
27586
27776
  return;
27587
27777
  }
27778
+ renderer.addTask(logicalId, `Deleting ${logicalId} (${resource.resourceType})`);
27588
27779
  try {
27589
27780
  const provider = destroyProviderRegistry.getProvider(resource.resourceType);
27590
27781
  let lastDeleteError;
@@ -27613,9 +27804,11 @@ Acquiring lock for stack ${stackName}...`);
27613
27804
  }
27614
27805
  if (lastDeleteError)
27615
27806
  throw lastDeleteError;
27807
+ renderer.removeTask(logicalId);
27616
27808
  logger.info(` \u2705 ${logicalId} (${resource.resourceType}) deleted`);
27617
27809
  deletedCount++;
27618
27810
  } catch (error) {
27811
+ renderer.removeTask(logicalId);
27619
27812
  const msg = error instanceof Error ? error.message : String(error);
27620
27813
  if (msg.includes("does not exist") || msg.includes("not found") || msg.includes("No policy found") || msg.includes("NoSuchEntity") || msg.includes("NotFoundException")) {
27621
27814
  logger.debug(` ${logicalId} already deleted, removing from state`);
@@ -27624,6 +27817,8 @@ Acquiring lock for stack ${stackName}...`);
27624
27817
  logger.error(` \u2717 Failed to delete ${logicalId}:`, String(error));
27625
27818
  errorCount++;
27626
27819
  }
27820
+ } finally {
27821
+ renderer.removeTask(logicalId);
27627
27822
  }
27628
27823
  });
27629
27824
  await Promise.all(deletePromises);
@@ -27639,6 +27834,7 @@ Acquiring lock for stack ${stackName}...`);
27639
27834
  \u2713 Stack ${stackName} destroyed (${deletedCount} deleted, ${errorCount} errors)`
27640
27835
  );
27641
27836
  } finally {
27837
+ renderer.stop();
27642
27838
  logger.debug("Releasing lock...");
27643
27839
  await lockManager.releaseLock(stackName);
27644
27840
  if (destroyAwsClients) {
@@ -27654,7 +27850,10 @@ Acquiring lock for stack ${stackName}...`);
27654
27850
  }
27655
27851
  }
27656
27852
  function createDestroyCommand() {
27657
- 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));
27658
27857
  [
27659
27858
  ...commonOptions,
27660
27859
  ...appOptions,
@@ -27768,7 +27967,7 @@ function reorderArgs(argv) {
27768
27967
  }
27769
27968
  async function main() {
27770
27969
  const program = new Command8();
27771
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.3.6");
27970
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.4.0");
27772
27971
  program.addCommand(createBootstrapCommand());
27773
27972
  program.addCommand(createSynthCommand());
27774
27973
  program.addCommand(createDeployCommand());