@go-to-k/cdkd 0.37.0 → 0.38.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
@@ -290,10 +290,16 @@ cdkd deploy MyStack \
290
290
  cdkd diff MyStack
291
291
 
292
292
  # Detect drift between cdkd state and AWS reality (state-only; no synth)
293
- # Exits 0 with no drift, 1 when drift is detected, 2 on error.
293
+ # Exits 0 with no drift, 1 when drift is detected, 2 on partial revert failure.
294
294
  cdkd drift MyStack
295
295
  cdkd drift --all --json
296
296
 
297
+ # Resolve drift: state ← AWS (catch up state with manual console changes)
298
+ cdkd drift MyStack --accept --yes
299
+
300
+ # Resolve drift: AWS ← state (push state values back into AWS via provider.update)
301
+ cdkd drift MyStack --revert --yes
302
+
297
303
  # Dry run (plan only, no changes)
298
304
  cdkd deploy --dry-run
299
305
 
package/dist/cli.js CHANGED
@@ -34066,6 +34066,7 @@ function createDiffCommand() {
34066
34066
  }
34067
34067
 
34068
34068
  // src/cli/commands/drift.ts
34069
+ import * as readline from "node:readline/promises";
34069
34070
  import { Command as Command6, Option as Option3 } from "commander";
34070
34071
  init_aws_clients();
34071
34072
 
@@ -34141,6 +34142,11 @@ async function driftCommand(stacks, options) {
34141
34142
  if (!options.all && stacks.length === 0) {
34142
34143
  throw new Error("Stack name is required. Usage: cdkd drift <stack> [<stack>...] | --all");
34143
34144
  }
34145
+ if (options.accept && options.revert) {
34146
+ throw new Error(
34147
+ "--accept and --revert are mutually exclusive. Use --accept to update cdkd state from AWS, or --revert to push cdkd state values back into AWS."
34148
+ );
34149
+ }
34144
34150
  await applyRoleArnIfSet({ roleArn: options.roleArn, region: options.region });
34145
34151
  const awsClients = new AwsClients({
34146
34152
  ...options.region && { region: options.region },
@@ -34151,14 +34157,11 @@ async function driftCommand(stacks, options) {
34151
34157
  const region = options.region || process.env["AWS_REGION"] || "us-east-1";
34152
34158
  const bucket = await resolveStateBucketWithDefault(options.stateBucket, region);
34153
34159
  const prefix = options.statePrefix;
34154
- const stateBackend = new S3StateBackend(
34155
- awsClients.s3,
34156
- { bucket, prefix },
34157
- {
34158
- region,
34159
- ...options.profile && { profile: options.profile }
34160
- }
34161
- );
34160
+ const stateConfig = { bucket, prefix };
34161
+ const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
34162
+ region,
34163
+ ...options.profile && { profile: options.profile }
34164
+ });
34162
34165
  await stateBackend.verifyBucketExists();
34163
34166
  const providerRegistry = new ProviderRegistry();
34164
34167
  registerAllProviders(providerRegistry);
@@ -34186,8 +34189,22 @@ async function driftCommand(stacks, options) {
34186
34189
  writeHumanReport(reports);
34187
34190
  }
34188
34191
  const drifted = reports.some((r) => r.outcomes.some((o) => o.kind === "drifted"));
34189
- if (drifted) {
34190
- throw new DriftDetectedError();
34192
+ if (!options.accept && !options.revert) {
34193
+ if (drifted) {
34194
+ throw new DriftDetectedError();
34195
+ }
34196
+ return;
34197
+ }
34198
+ if (!drifted) {
34199
+ logger.info(
34200
+ options.accept ? "No drift detected \u2014 nothing to accept." : "No drift detected \u2014 nothing to revert."
34201
+ );
34202
+ return;
34203
+ }
34204
+ if (options.accept) {
34205
+ await runAccept(reports, stateBackend, stateConfig, awsClients, options);
34206
+ } else {
34207
+ await runRevert(reports, providerRegistry, stateConfig, awsClients, options);
34191
34208
  }
34192
34209
  } finally {
34193
34210
  awsClients.destroy();
@@ -34288,13 +34305,261 @@ async function runDriftForStack(stackName, region, stateBackend, providerRegistr
34288
34305
  kind: "drifted",
34289
34306
  logicalId,
34290
34307
  resourceType: resource.resourceType,
34291
- changes
34308
+ changes,
34309
+ awsProperties: aws
34292
34310
  });
34293
34311
  }
34294
34312
  }
34295
- return { stackName, region, outcomes };
34313
+ return {
34314
+ stackName,
34315
+ region,
34316
+ outcomes,
34317
+ state,
34318
+ etag: result.etag,
34319
+ migrationPending: result.migrationPending ?? false
34320
+ };
34296
34321
  });
34297
34322
  }
34323
+ function setAtPath(target, path, value) {
34324
+ if (path.length === 0) {
34325
+ return;
34326
+ }
34327
+ const segments = path.split(".");
34328
+ let cursor = target;
34329
+ for (let i = 0; i < segments.length - 1; i++) {
34330
+ const key = segments[i];
34331
+ const next = cursor[key];
34332
+ if (next === void 0 || next === null || typeof next !== "object" || Array.isArray(next)) {
34333
+ const fresh = {};
34334
+ cursor[key] = fresh;
34335
+ cursor = fresh;
34336
+ } else {
34337
+ cursor = next;
34338
+ }
34339
+ }
34340
+ cursor[segments[segments.length - 1]] = value;
34341
+ }
34342
+ async function runAccept(reports, stateBackend, stateConfig, awsClients, options) {
34343
+ const logger = getLogger();
34344
+ printAcceptPlan(reports);
34345
+ if (options.dryRun) {
34346
+ logger.info("--dry-run: state will NOT be written. Re-run without --dry-run to apply.");
34347
+ return;
34348
+ }
34349
+ if (!options.yes) {
34350
+ const ok = await confirmPrompt(`Update cdkd state with the AWS-current values shown above?`);
34351
+ if (!ok) {
34352
+ logger.info("Aborted.");
34353
+ return;
34354
+ }
34355
+ }
34356
+ const lockManager = new LockManager(awsClients.s3, stateConfig);
34357
+ const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
34358
+ for (const report of reports) {
34359
+ const driftedOutcomes = report.outcomes.filter(
34360
+ (o) => o.kind === "drifted"
34361
+ );
34362
+ if (driftedOutcomes.length === 0) {
34363
+ continue;
34364
+ }
34365
+ await lockManager.acquireLock(report.stackName, report.region, owner, "drift-accept");
34366
+ try {
34367
+ const resources = { ...report.state.resources };
34368
+ for (const outcome of driftedOutcomes) {
34369
+ const existing = resources[outcome.logicalId];
34370
+ if (!existing)
34371
+ continue;
34372
+ const newProperties = JSON.parse(JSON.stringify(existing.properties ?? {}));
34373
+ for (const change of outcome.changes) {
34374
+ setAtPath(newProperties, change.path, change.awsValue);
34375
+ }
34376
+ resources[outcome.logicalId] = {
34377
+ ...existing,
34378
+ properties: newProperties
34379
+ };
34380
+ }
34381
+ const newState = {
34382
+ ...report.state,
34383
+ resources,
34384
+ lastModified: Date.now()
34385
+ };
34386
+ const saveOptions = {
34387
+ expectedEtag: report.etag
34388
+ };
34389
+ if (report.migrationPending) {
34390
+ saveOptions.migrateLegacy = true;
34391
+ }
34392
+ await stateBackend.saveState(report.stackName, report.region, newState, saveOptions);
34393
+ logger.info(
34394
+ `\u2713 State updated for ${report.stackName} (${report.region}): accepted drift on ${driftedOutcomes.length} resource(s).`
34395
+ );
34396
+ } finally {
34397
+ await lockManager.releaseLock(report.stackName, report.region).catch((err) => {
34398
+ logger.warn(
34399
+ `Failed to release lock for ${report.stackName} (${report.region}): ` + (err instanceof Error ? err.message : String(err))
34400
+ );
34401
+ });
34402
+ }
34403
+ }
34404
+ }
34405
+ async function runRevert(reports, providerRegistry, stateConfig, awsClients, options) {
34406
+ const logger = getLogger();
34407
+ printRevertPlan(reports);
34408
+ if (options.dryRun) {
34409
+ logger.info("--dry-run: AWS will NOT be modified. Re-run without --dry-run to apply.");
34410
+ return;
34411
+ }
34412
+ if (!options.yes) {
34413
+ const ok = await confirmPrompt(
34414
+ `Push cdkd state values back into AWS for the resources shown above?`
34415
+ );
34416
+ if (!ok) {
34417
+ logger.info("Aborted.");
34418
+ return;
34419
+ }
34420
+ }
34421
+ const lockManager = new LockManager(awsClients.s3, stateConfig);
34422
+ const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
34423
+ const concurrency = Math.max(1, options.concurrency ?? 4);
34424
+ let totalFailed = 0;
34425
+ let totalSucceeded = 0;
34426
+ for (const report of reports) {
34427
+ const driftedOutcomes = report.outcomes.filter(
34428
+ (o) => o.kind === "drifted"
34429
+ );
34430
+ if (driftedOutcomes.length === 0) {
34431
+ continue;
34432
+ }
34433
+ await lockManager.acquireLock(report.stackName, report.region, owner, "drift-revert");
34434
+ try {
34435
+ const tasks = driftedOutcomes.map((outcome) => async () => {
34436
+ const stateResource = report.state.resources[outcome.logicalId];
34437
+ if (!stateResource) {
34438
+ totalFailed++;
34439
+ logger.error(
34440
+ ` \u2717 ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): resource missing from state; skipped.`
34441
+ );
34442
+ return;
34443
+ }
34444
+ const provider = providerRegistry.getProvider(outcome.resourceType);
34445
+ try {
34446
+ await withRetry(
34447
+ () => provider.update(
34448
+ outcome.logicalId,
34449
+ stateResource.physicalId,
34450
+ outcome.resourceType,
34451
+ stateResource.properties ?? {},
34452
+ outcome.awsProperties
34453
+ ),
34454
+ outcome.logicalId,
34455
+ { logger: { debug: (msg) => logger.debug(msg) } }
34456
+ );
34457
+ totalSucceeded++;
34458
+ logger.info(
34459
+ ` \u2713 ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): reverted.`
34460
+ );
34461
+ } catch (err) {
34462
+ totalFailed++;
34463
+ const msg = err instanceof Error ? err.message : String(err);
34464
+ logger.error(
34465
+ ` \u2717 ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): ${msg}`
34466
+ );
34467
+ }
34468
+ });
34469
+ await runWithConcurrency(tasks, concurrency);
34470
+ } finally {
34471
+ await lockManager.releaseLock(report.stackName, report.region).catch((err) => {
34472
+ logger.warn(
34473
+ `Failed to release lock for ${report.stackName} (${report.region}): ` + (err instanceof Error ? err.message : String(err))
34474
+ );
34475
+ });
34476
+ }
34477
+ }
34478
+ logger.info(`
34479
+ Revert summary: ${totalSucceeded} reverted, ${totalFailed} failed.`);
34480
+ if (totalFailed > 0) {
34481
+ throw new PartialFailureError(
34482
+ `Revert completed with ${totalFailed} resource error(s). Re-run 'cdkd drift <stack>' to see the remaining drift, then 'cdkd drift <stack> --revert' to retry.`
34483
+ );
34484
+ }
34485
+ }
34486
+ function printAcceptPlan(reports) {
34487
+ for (const report of reports) {
34488
+ const drifted = report.outcomes.filter(
34489
+ (o) => o.kind === "drifted"
34490
+ );
34491
+ if (drifted.length === 0)
34492
+ continue;
34493
+ process.stdout.write(
34494
+ `
34495
+ Plan (--accept): update cdkd state for ${report.stackName} (${report.region}):
34496
+ `
34497
+ );
34498
+ for (const o of drifted) {
34499
+ process.stdout.write(` ~ ${o.logicalId} (${o.resourceType})
34500
+ `);
34501
+ for (const change of o.changes) {
34502
+ process.stdout.write(
34503
+ ` ${change.path}: ${formatScalar(change.stateValue)} -> ${formatScalar(change.awsValue)}
34504
+ `
34505
+ );
34506
+ }
34507
+ }
34508
+ }
34509
+ }
34510
+ function printRevertPlan(reports) {
34511
+ for (const report of reports) {
34512
+ const drifted = report.outcomes.filter(
34513
+ (o) => o.kind === "drifted"
34514
+ );
34515
+ if (drifted.length === 0)
34516
+ continue;
34517
+ process.stdout.write(
34518
+ `
34519
+ Plan (--revert): push cdkd state values back into AWS for ${report.stackName} (${report.region}):
34520
+ `
34521
+ );
34522
+ for (const o of drifted) {
34523
+ const word = o.changes.length === 1 ? "property path" : "property paths";
34524
+ process.stdout.write(
34525
+ ` \u2192 provider.update on ${o.logicalId} (${o.resourceType}): revert ${o.changes.length} ${word}
34526
+ `
34527
+ );
34528
+ for (const change of o.changes) {
34529
+ process.stdout.write(
34530
+ ` ${change.path}: ${formatScalar(change.awsValue)} -> ${formatScalar(change.stateValue)}
34531
+ `
34532
+ );
34533
+ }
34534
+ }
34535
+ }
34536
+ }
34537
+ async function runWithConcurrency(tasks, concurrency) {
34538
+ const queue = [...tasks];
34539
+ const workers = [];
34540
+ for (let i = 0; i < Math.min(concurrency, queue.length); i++) {
34541
+ workers.push(
34542
+ (async () => {
34543
+ while (queue.length > 0) {
34544
+ const task = queue.shift();
34545
+ if (!task)
34546
+ break;
34547
+ await task();
34548
+ }
34549
+ })()
34550
+ );
34551
+ }
34552
+ await Promise.all(workers);
34553
+ }
34554
+ async function confirmPrompt(prompt) {
34555
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
34556
+ try {
34557
+ const ans = await rl.question(`${prompt} [y/N] `);
34558
+ return /^y(es)?$/i.test(ans.trim());
34559
+ } finally {
34560
+ rl.close();
34561
+ }
34562
+ }
34298
34563
  function writeJsonReport(reports) {
34299
34564
  const payload = reports.map((r) => {
34300
34565
  const drifted = r.outcomes.filter((o) => o.kind === "drifted").map((o) => ({ logicalId: o.logicalId, type: o.resourceType, changes: o.changes }));
@@ -34371,8 +34636,25 @@ function stackRegionOption() {
34371
34636
  }
34372
34637
  function createDriftCommand() {
34373
34638
  const cmd = new Command6("drift").description(
34374
- "Detect drift between cdkd state and AWS reality. Exits 0 when no drift, 1 when drift is detected."
34375
- ).argument("[stacks...]", "Stack name(s) to check (physical CloudFormation names)").option("--all", "Check every stack in the state bucket", false).option("--json", "Output as JSON", false).addOption(stackRegionOption()).action(withErrorHandling(driftCommand));
34639
+ "Detect drift between cdkd state and AWS reality. Exits 0 when no drift, 1 when drift is detected. Pass --accept to update cdkd state from AWS, or --revert to push cdkd state values back into AWS."
34640
+ ).argument("[stacks...]", "Stack name(s) to check (physical CloudFormation names)").option("--all", "Check every stack in the state bucket", false).option("--json", "Output as JSON", false).option(
34641
+ "--accept",
34642
+ "Update cdkd state with the AWS-current values for every drifted property (state \u2190 AWS). Mutually exclusive with --revert.",
34643
+ false
34644
+ ).option(
34645
+ "--revert",
34646
+ "Push cdkd state values back into AWS via provider.update for every drifted resource (AWS \u2190 state). Mutually exclusive with --accept.",
34647
+ false
34648
+ ).option(
34649
+ "--dry-run",
34650
+ "Print the planned mutations without acquiring a lock or hitting AWS / S3. Honored by --accept and --revert.",
34651
+ false
34652
+ ).option(
34653
+ "--concurrency <number>",
34654
+ "Maximum concurrent provider.update calls during --revert",
34655
+ (value) => parseInt(value, 10),
34656
+ 4
34657
+ ).addOption(stackRegionOption()).action(withErrorHandling(driftCommand));
34376
34658
  [...commonOptions, ...stateOptions].forEach((opt) => cmd.addOption(opt));
34377
34659
  cmd.addOption(deprecatedRegionOption);
34378
34660
  return cmd;
@@ -34383,7 +34665,7 @@ import { Command as Command7 } from "commander";
34383
34665
  init_aws_clients();
34384
34666
 
34385
34667
  // src/cli/commands/destroy-runner.ts
34386
- import * as readline from "node:readline/promises";
34668
+ import * as readline2 from "node:readline/promises";
34387
34669
  init_aws_clients();
34388
34670
  async function runDestroyForStack(stackName, state, ctx) {
34389
34671
  const logger = getLogger();
@@ -34409,7 +34691,7 @@ Resources to be deleted (${resourceCount}):`);
34409
34691
  logger.info(` - ${logicalId} (${resource.resourceType})`);
34410
34692
  }
34411
34693
  if (!ctx.skipConfirmation) {
34412
- const rl = readline.createInterface({
34694
+ const rl = readline2.createInterface({
34413
34695
  input: process.stdin,
34414
34696
  output: process.stdout
34415
34697
  });
@@ -34820,7 +35102,7 @@ function createDestroyCommand() {
34820
35102
  }
34821
35103
 
34822
35104
  // src/cli/commands/orphan.ts
34823
- import * as readline2 from "node:readline/promises";
35105
+ import * as readline3 from "node:readline/promises";
34824
35106
  import { Command as Command8 } from "commander";
34825
35107
  init_aws_clients();
34826
35108
 
@@ -35353,7 +35635,7 @@ Re-run with --force to fall back to cached attribute values from state, or fix t
35353
35635
  return;
35354
35636
  }
35355
35637
  if (!options.yes && !options.force) {
35356
- const ok = await confirmPrompt(
35638
+ const ok = await confirmPrompt2(
35357
35639
  `Orphan ${orphanLogicalIds.length} resource(s) from cdkd state for ${stackInfo.stackName} (${targetRegion})? AWS resources will NOT be deleted.`
35358
35640
  );
35359
35641
  if (!ok) {
@@ -35492,8 +35774,8 @@ function stringifyForAudit(value) {
35492
35774
  return JSON.stringify(value);
35493
35775
  return JSON.stringify(value);
35494
35776
  }
35495
- async function confirmPrompt(prompt) {
35496
- const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
35777
+ async function confirmPrompt2(prompt) {
35778
+ const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
35497
35779
  try {
35498
35780
  const ans = await rl.question(`${prompt} [y/N] `);
35499
35781
  return /^y(es)?$/i.test(ans.trim());
@@ -35768,7 +36050,7 @@ function createForceUnlockCommand() {
35768
36050
  }
35769
36051
 
35770
36052
  // src/cli/commands/state.ts
35771
- import * as readline4 from "node:readline/promises";
36053
+ import * as readline5 from "node:readline/promises";
35772
36054
  import { Command as Command12, Option as Option6 } from "commander";
35773
36055
  import {
35774
36056
  GetBucketLocationCommand as GetBucketLocationCommand2,
@@ -35778,7 +36060,7 @@ import {
35778
36060
  init_aws_clients();
35779
36061
 
35780
36062
  // src/cli/commands/state-migrate.ts
35781
- import * as readline3 from "node:readline/promises";
36063
+ import * as readline4 from "node:readline/promises";
35782
36064
  import { Command as Command11 } from "commander";
35783
36065
  import {
35784
36066
  CopyObjectCommand,
@@ -35848,7 +36130,7 @@ async function stateMigrateCommand(options) {
35848
36130
  }
35849
36131
  if (!options.yes) {
35850
36132
  const action = options.removeLegacy ? "and DELETE the source bucket" : "(source bucket will be kept)";
35851
- const ok = await confirmPrompt2(
36133
+ const ok = await confirmPrompt3(
35852
36134
  `Copy ${sourceObjects.length} object(s) from ${legacyBucket} -> ${newBucket} ${action}?`
35853
36135
  );
35854
36136
  if (!ok) {
@@ -36053,8 +36335,8 @@ async function emptyBucketAllVersions(s3, bucket) {
36053
36335
  versionIdMarker = resp.NextVersionIdMarker;
36054
36336
  } while (keyMarker || versionIdMarker);
36055
36337
  }
36056
- async function confirmPrompt2(prompt) {
36057
- const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
36338
+ async function confirmPrompt3(prompt) {
36339
+ const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
36058
36340
  try {
36059
36341
  const ans = await rl.question(`${prompt} [y/N] `);
36060
36342
  return /^y(es)?$/i.test(ans.trim());
@@ -36462,7 +36744,7 @@ Use 'cdkd destroy ${stackName}' if you want to delete the actual resources.
36462
36744
 
36463
36745
  `
36464
36746
  );
36465
- const rl = readline4.createInterface({
36747
+ const rl = readline5.createInterface({
36466
36748
  input: process.stdin,
36467
36749
  output: process.stdout
36468
36750
  });
@@ -36553,7 +36835,7 @@ WARNING: This destroys ${stackNames.length} stack(s) and removes their state rec
36553
36835
  `);
36554
36836
  }
36555
36837
  process.stdout.write("\n");
36556
- const rl = readline4.createInterface({
36838
+ const rl = readline5.createInterface({
36557
36839
  input: process.stdin,
36558
36840
  output: process.stdout
36559
36841
  });
@@ -36795,12 +37077,12 @@ function createStateCommand() {
36795
37077
 
36796
37078
  // src/cli/commands/import.ts
36797
37079
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
36798
- import * as readline6 from "node:readline/promises";
37080
+ import * as readline7 from "node:readline/promises";
36799
37081
  import { Command as Command13 } from "commander";
36800
37082
  init_aws_clients();
36801
37083
 
36802
37084
  // src/cli/commands/retire-cfn-stack.ts
36803
- import * as readline5 from "node:readline/promises";
37085
+ import * as readline6 from "node:readline/promises";
36804
37086
  import {
36805
37087
  DescribeStacksCommand,
36806
37088
  DescribeStackResourcesCommand,
@@ -36845,7 +37127,7 @@ async function retireCloudFormationStack(options) {
36845
37127
  }
36846
37128
  const { body: newBody, modified } = injectRetainPolicies(tpl.TemplateBody, cfnStackName);
36847
37129
  if (!yes) {
36848
- const ok = await confirmPrompt3(
37130
+ const ok = await confirmPrompt4(
36849
37131
  `Set DeletionPolicy=Retain and UpdateReplacePolicy=Retain on every resource in CloudFormation stack '${cfnStackName}', then delete the stack? AWS resources will NOT be deleted (cdkd state has been written).`
36850
37132
  );
36851
37133
  if (!ok) {
@@ -37005,8 +37287,8 @@ async function getCloudFormationResourceMapping(cfnStackName, cfnClient) {
37005
37287
  }
37006
37288
  return map;
37007
37289
  }
37008
- async function confirmPrompt3(prompt) {
37009
- const rl = readline5.createInterface({ input: process.stdin, output: process.stdout });
37290
+ async function confirmPrompt4(prompt) {
37291
+ const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
37010
37292
  try {
37011
37293
  const ans = await rl.question(`${prompt} [y/N] `);
37012
37294
  return /^y(es)?$/i.test(ans.trim());
@@ -37205,7 +37487,7 @@ async function importCommand(stackArg, options) {
37205
37487
  const preservedCount = selectiveMode && existingState ? Object.keys(existingState.resources).filter((id) => !overrides.has(id)).length : 0;
37206
37488
  const totalAfter = importedCount + preservedCount;
37207
37489
  const breakdown = preservedCount > 0 ? ` (${importedCount} new/overwritten + ${preservedCount} preserved)` : "";
37208
- const ok = await confirmPrompt4(
37490
+ const ok = await confirmPrompt5(
37209
37491
  `Write state for ${stackInfo.stackName} (${targetRegion}) with ${totalAfter} resource(s)${breakdown}?`
37210
37492
  );
37211
37493
  if (!ok) {
@@ -37466,8 +37748,8 @@ function formatOutcome(outcome) {
37466
37748
  return "\u2717";
37467
37749
  }
37468
37750
  }
37469
- async function confirmPrompt4(prompt) {
37470
- const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
37751
+ async function confirmPrompt5(prompt) {
37752
+ const rl = readline7.createInterface({ input: process.stdin, output: process.stdout });
37471
37753
  try {
37472
37754
  const ans = await rl.question(`${prompt} [y/N] `);
37473
37755
  return /^y(es)?$/i.test(ans.trim());
@@ -37544,7 +37826,7 @@ function reorderArgs(argv) {
37544
37826
  }
37545
37827
  async function main() {
37546
37828
  const program = new Command14();
37547
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.37.0");
37829
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.38.0");
37548
37830
  program.addCommand(createBootstrapCommand());
37549
37831
  program.addCommand(createSynthCommand());
37550
37832
  program.addCommand(createListCommand());