@go-to-k/cdkd 0.228.0 → 0.229.1

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
@@ -589,14 +589,17 @@ cdkd events MyStack # list runs, newest first
589
589
  cdkd events MyStack --run <runId> # one run's full event stream
590
590
  cdkd events MyStack --format json # machine-readable (AI-agent hand-off)
591
591
  cdkd events prune MyStack --all # purge event history (reclaim S3 space)
592
+ cdkd destroy MyStack --purge-events # destroy + purge events in one command
592
593
  ```
593
594
 
594
595
  Events are persisted as JSONL under a `deployments/` key family separate
595
596
  from `state.json` (no state schema bump), so a destroyed stack's failure
596
597
  history stays readable. Recording is best-effort and never blocks the
597
598
  run; events carry error + metadata only (never resource properties). The
598
- store self-bounds to the last 20 runs, and `cdkd events prune` purges old
599
- history on demand (`--keep N` / `--older-than <dur>` / `--all`). See
599
+ store self-bounds to the last 20 runs, `cdkd events prune` purges old
600
+ history on demand (`--keep N` / `--older-than <dur>` / `--all`), and
601
+ `cdkd destroy --purge-events` deletes a stack's history right after a clean
602
+ destroy so the bucket returns fully empty. See
600
603
  **[docs/deployment-events.md](docs/deployment-events.md)** for the full
601
604
  reference.
602
605
 
package/dist/cli.js CHANGED
@@ -1556,7 +1556,7 @@ const FLUSH_INTERVAL_MS = 2e3;
1556
1556
  const FLUSH_EVENT_THRESHOLD = 50;
1557
1557
  /** Build-time cdkd version, with a dev fallback for non-built contexts. */
1558
1558
  function getCdkdVersion() {
1559
- return "0.228.0";
1559
+ return "0.229.1";
1560
1560
  }
1561
1561
  /**
1562
1562
  * Generate a time-sortable unique run id, e.g.
@@ -2505,10 +2505,16 @@ var ExportIndexStore = class {
2505
2505
  *
2506
2506
  * @param stacks the stacks being deployed / destroyed together (the `--all`
2507
2507
  * set, or the auto-include-dependency-expanded target set).
2508
+ * @param opts see {@link InferCrossStackStackDepsOptions}. `opts.kinds`
2509
+ * restricts which reference kinds count as edges (defaults to both). Pass
2510
+ * `['ImportValue']` for STRONG-only edges (deploy-closure expansion).
2508
2511
  * @returns Map<consumerStackName, Set<producerStackName>>. Consumers with no
2509
2512
  * inferred cross-stack producer get an empty set entry.
2510
2513
  */
2511
- function inferCrossStackStackDeps(stacks) {
2514
+ function inferCrossStackStackDeps(stacks, opts) {
2515
+ const kinds = opts?.kinds ?? ["ImportValue", "GetStackOutput"];
2516
+ const wantImportValue = kinds.includes("ImportValue");
2517
+ const wantGetStackOutput = kinds.includes("GetStackOutput");
2512
2518
  const exportOwner = /* @__PURE__ */ new Map();
2513
2519
  for (const stack of stacks) {
2514
2520
  const outputs = stack.template.Outputs;
@@ -2527,10 +2533,14 @@ function inferCrossStackStackDeps(stacks) {
2527
2533
  collectCrossStackRefs(stack.template, (kind, value) => {
2528
2534
  let producer;
2529
2535
  if (kind === "ImportValue") {
2536
+ if (!wantImportValue) return;
2530
2537
  if (typeof value === "string") producer = exportOwner.get(value);
2531
- } else if (value && typeof value === "object" && !Array.isArray(value)) {
2532
- const sn = value["StackName"];
2533
- if (typeof sn === "string") producer = sn;
2538
+ } else {
2539
+ if (!wantGetStackOutput) return;
2540
+ if (value && typeof value === "object" && !Array.isArray(value)) {
2541
+ const sn = value["StackName"];
2542
+ if (typeof sn === "string") producer = sn;
2543
+ }
2534
2544
  }
2535
2545
  if (producer && producer !== stack.stackName && stackNames.has(producer)) producers.add(producer);
2536
2546
  });
@@ -36835,13 +36845,19 @@ async function deployCommand(stacks, options) {
36835
36845
  for (const producer of inferredCrossStackDeps.get(stackName) ?? []) union.add(producer);
36836
36846
  return union;
36837
36847
  };
36848
+ const inferredStrongCrossStackDeps = inferCrossStackStackDeps(allStacks, { kinds: ["ImportValue"] });
36849
+ const effectiveStrongStackDeps = (stackName, deps) => {
36850
+ const union = new Set(deps);
36851
+ for (const producer of inferredStrongCrossStackDeps.get(stackName) ?? []) union.add(producer);
36852
+ return union;
36853
+ };
36838
36854
  if (!options.exclusively) {
36839
36855
  const targetNames = new Set(targetStacks.map((s) => s.stackName));
36840
36856
  const allStackMap = new Map(allStacks.map((s) => [s.stackName, s]));
36841
36857
  const addDependencies = (stackName) => {
36842
36858
  const stack = allStackMap.get(stackName);
36843
36859
  if (!stack) return;
36844
- for (const depName of effectiveStackDeps(stackName, stack.dependencyNames)) if (!targetNames.has(depName)) {
36860
+ for (const depName of effectiveStrongStackDeps(stackName, stack.dependencyNames)) if (!targetNames.has(depName)) {
36845
36861
  const depStack = allStackMap.get(depName);
36846
36862
  if (depStack) {
36847
36863
  targetNames.add(depName);
@@ -39418,6 +39434,37 @@ function orderConsumersBeforeProducers(stackNames, consumerToProducers) {
39418
39434
  return ordered;
39419
39435
  }
39420
39436
  /**
39437
+ * Issue [#885] — apply `cdkd destroy --purge-events` for one stack.
39438
+ *
39439
+ * Purges the stack's deployment-event history (the post-mortem `deployments/`
39440
+ * store cdkd keeps by default) ONLY after a clean, non-interrupted destroy, so
39441
+ * the state bucket returns fully empty. Deliberately skipped on a failed /
39442
+ * interrupted destroy — those events ARE the post-mortem the user wants on the
39443
+ * retry — and a no-op when `--purge-events` was not passed.
39444
+ *
39445
+ * MUST be called AFTER the run's `eventRecorder.finalize()` (which writes this
39446
+ * run's own events + index): purging first would just be re-created by the
39447
+ * finalize. Best-effort — a purge failure warns but never fails the
39448
+ * already-successful destroy (the resources are gone regardless). The
39449
+ * equivalent for an already-destroyed stack is `cdkd events prune <stack> --all`.
39450
+ *
39451
+ * Returns the prune result when a purge ran, or `null` when it was skipped
39452
+ * (flag off / failed / interrupted) or the purge itself errored. Extracted
39453
+ * from the destroy loop so the gating is unit-testable without the full
39454
+ * synth / AWS-client harness.
39455
+ */
39456
+ async function purgeEventsAfterDestroy(reader, stackName, region, opts, logger) {
39457
+ if (opts.purgeEvents !== true || opts.runResult !== "SUCCEEDED" || opts.interrupted) return null;
39458
+ try {
39459
+ const purge = await reader.pruneRuns(stackName, region, { all: true });
39460
+ if (purge.deletedRunIds.length > 0 || purge.indexDeleted) logger.info(` Purged deployment-event history for ${stackName} (${region}).`);
39461
+ return purge;
39462
+ } catch (purgeError) {
39463
+ logger.warn(` Failed to purge deployment-event history for ${stackName}: ${purgeError instanceof Error ? purgeError.message : String(purgeError)}`);
39464
+ return null;
39465
+ }
39466
+ }
39467
+ /**
39421
39468
  * Destroy command implementation
39422
39469
  */
39423
39470
  async function destroyCommand(stackArgs, options) {
@@ -39645,6 +39692,11 @@ async function destroyCommand(stackArgs, options) {
39645
39692
  } finally {
39646
39693
  await eventRecorder.finalize(destroyRunResult);
39647
39694
  }
39695
+ await purgeEventsAfterDestroy(new DeploymentEventsReader(stateBackend), stackName, stackTargetRegion, {
39696
+ purgeEvents: options.purgeEvents,
39697
+ runResult: destroyRunResult,
39698
+ interrupted
39699
+ }, logger);
39648
39700
  if (interrupted) break;
39649
39701
  }
39650
39702
  if (totalErrors > 0) throw new PartialFailureError(`Destroy completed with ${totalErrors} resource error(s). State preserved — inspect 'cdkd state show <stack>' and re-run 'cdkd destroy' to retry.`);
@@ -39657,7 +39709,7 @@ async function destroyCommand(stackArgs, options) {
39657
39709
  * Create destroy command
39658
39710
  */
39659
39711
  function createDestroyCommand() {
39660
- const cmd = new Command("destroy").description("Destroy all resources in the stack").argument("[stacks...]", "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/*').").option("--all", "Destroy all stacks", false).action(withErrorHandling(destroyCommand));
39712
+ const cmd = new Command("destroy").description("Destroy all resources in the stack").argument("[stacks...]", "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/*').").option("--all", "Destroy all stacks", false).option("--purge-events", "After a clean destroy, also delete the stack's deployment-event history (issue #808 store) so the state bucket returns fully empty. By default events survive destroy as post-mortem context. Skipped when the destroy fails or is interrupted (those events aid the retry). Equivalent for an already-destroyed stack: 'cdkd events prune <stack> --all'.", false).action(withErrorHandling(destroyCommand));
39661
39713
  [
39662
39714
  ...commonOptions,
39663
39715
  ...appOptions,
@@ -54952,7 +55004,7 @@ function reorderArgs(argv) {
54952
55004
  async function main() {
54953
55005
  installPipeCloseHandler();
54954
55006
  const program = new Command();
54955
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.228.0");
55007
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.229.1");
54956
55008
  program.addCommand(createBootstrapCommand());
54957
55009
  program.addCommand(createSynthCommand());
54958
55010
  program.addCommand(createListCommand());