@go-to-k/cdkd 0.34.0 → 0.35.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
@@ -282,6 +282,11 @@ cdkd deploy MyStack \
282
282
  # Show diff (what would change)
283
283
  cdkd diff MyStack
284
284
 
285
+ # Detect drift between cdkd state and AWS reality (state-only; no synth)
286
+ # Exits 0 with no drift, 1 when drift is detected, 2 on error.
287
+ cdkd drift MyStack
288
+ cdkd drift --all --json
289
+
285
290
  # Dry run (plan only, no changes)
286
291
  cdkd deploy --dry-run
287
292
 
package/dist/cli.js CHANGED
@@ -447,7 +447,7 @@ var init_aws_clients = __esm({
447
447
  });
448
448
 
449
449
  // src/cli/index.ts
450
- import { Command as Command13 } from "commander";
450
+ import { Command as Command14 } from "commander";
451
451
 
452
452
  // src/cli/commands/bootstrap.ts
453
453
  import { Command, Option as Option2 } from "commander";
@@ -1206,7 +1206,10 @@ Caused by: ${error.cause.message}`;
1206
1206
  }
1207
1207
  function handleError(error) {
1208
1208
  const logger = getLogger();
1209
- logger.error(formatError(error));
1209
+ const silent = error instanceof CdkdError && error.silent;
1210
+ if (!silent) {
1211
+ logger.error(formatError(error));
1212
+ }
1210
1213
  if (error instanceof Error && error.stack) {
1211
1214
  logger.debug("Stack trace:", error.stack);
1212
1215
  }
@@ -7579,6 +7582,50 @@ Error: ${err.message || "Unknown error"}`,
7579
7582
  }
7580
7583
  return resourceType.startsWith("AWS::");
7581
7584
  }
7585
+ /**
7586
+ * Read the AWS-current properties of a resource managed via Cloud Control
7587
+ * API, for `cdkd drift` comparison.
7588
+ *
7589
+ * Strategy: `GetResource(TypeName, Identifier)` returns `ResourceModel` as
7590
+ * a JSON string of every property AWS reports for the resource. Parse and
7591
+ * surface it as the AWS-current snapshot — the drift command intersects
7592
+ * this against the keys present in cdkd state, so AWS-only keys (timestamps,
7593
+ * generated ids, etc.) are filtered out at compare time.
7594
+ *
7595
+ * Returns `undefined` for the unique cases that mean "drift unknown" (the
7596
+ * resource was deleted out from under cdkd, or the response had no
7597
+ * Properties field). Re-throws on any other error so the drift command can
7598
+ * surface throttling / access-denied issues to the user.
7599
+ *
7600
+ * This single CC API implementation gives drift detection coverage to every
7601
+ * resource type that goes through CC API — the majority of cdkd's surface.
7602
+ * SDK Providers add their own `readCurrentState` incrementally (PR D).
7603
+ */
7604
+ async readCurrentState(physicalId, _logicalId, resourceType) {
7605
+ try {
7606
+ const response = await this.cloudControlClient.send(
7607
+ new GetResourceCommand2({
7608
+ TypeName: resourceType,
7609
+ Identifier: physicalId
7610
+ })
7611
+ );
7612
+ const raw = response.ResourceDescription?.Properties;
7613
+ if (typeof raw !== "string" || raw.length === 0) {
7614
+ return void 0;
7615
+ }
7616
+ const parsed = JSON.parse(raw);
7617
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
7618
+ return void 0;
7619
+ }
7620
+ return parsed;
7621
+ } catch (error) {
7622
+ const err = error;
7623
+ if (err.name === "ResourceNotFoundException") {
7624
+ return void 0;
7625
+ }
7626
+ throw error;
7627
+ }
7628
+ }
7582
7629
  /**
7583
7630
  * Adopt an already-deployed resource into cdkd state via Cloud Control API.
7584
7631
  *
@@ -33456,8 +33503,321 @@ function createDiffCommand() {
33456
33503
  return cmd;
33457
33504
  }
33458
33505
 
33506
+ // src/cli/commands/drift.ts
33507
+ import { Command as Command6, Option as Option3 } from "commander";
33508
+ init_aws_clients();
33509
+
33510
+ // src/analyzer/drift-calculator.ts
33511
+ function calculateResourceDrift(stateProperties, awsProperties) {
33512
+ const drifts = [];
33513
+ for (const key of Object.keys(stateProperties)) {
33514
+ diffAt(key, stateProperties[key], awsProperties[key], drifts);
33515
+ }
33516
+ return drifts;
33517
+ }
33518
+ function diffAt(path, stateValue, awsValue, out) {
33519
+ if (deepEqual(stateValue, awsValue))
33520
+ return;
33521
+ if (isPlainObject(stateValue) && isPlainObject(awsValue) && !Array.isArray(stateValue) && !Array.isArray(awsValue)) {
33522
+ for (const key of Object.keys(stateValue)) {
33523
+ diffAt(`${path}.${key}`, stateValue[key], awsValue[key], out);
33524
+ }
33525
+ return;
33526
+ }
33527
+ out.push({ path, stateValue, awsValue });
33528
+ }
33529
+ function deepEqual(a, b) {
33530
+ if (a === b)
33531
+ return true;
33532
+ if (a === null || b === null || a === void 0 || b === void 0) {
33533
+ return a === b;
33534
+ }
33535
+ if (typeof a !== typeof b)
33536
+ return false;
33537
+ if (typeof a !== "object")
33538
+ return false;
33539
+ if (Array.isArray(a) || Array.isArray(b)) {
33540
+ if (!Array.isArray(a) || !Array.isArray(b))
33541
+ return false;
33542
+ if (a.length !== b.length)
33543
+ return false;
33544
+ return a.every((v, i) => deepEqual(v, b[i]));
33545
+ }
33546
+ const aObj = a;
33547
+ const bObj = b;
33548
+ const aKeys = Object.keys(aObj);
33549
+ const bKeys = Object.keys(bObj);
33550
+ if (aKeys.length !== bKeys.length)
33551
+ return false;
33552
+ for (const key of aKeys) {
33553
+ if (!Object.prototype.hasOwnProperty.call(bObj, key))
33554
+ return false;
33555
+ if (!deepEqual(aObj[key], bObj[key]))
33556
+ return false;
33557
+ }
33558
+ return true;
33559
+ }
33560
+ function isPlainObject(value) {
33561
+ return typeof value === "object" && value !== null;
33562
+ }
33563
+
33564
+ // src/cli/commands/drift.ts
33565
+ var DriftDetectedError = class _DriftDetectedError extends CdkdError {
33566
+ silent = true;
33567
+ constructor() {
33568
+ super("drift detected", "DRIFT_DETECTED");
33569
+ this.name = "DriftDetectedError";
33570
+ Object.setPrototypeOf(this, _DriftDetectedError.prototype);
33571
+ }
33572
+ };
33573
+ async function driftCommand(stacks, options) {
33574
+ const logger = getLogger();
33575
+ if (options.verbose) {
33576
+ logger.setLevel("debug");
33577
+ }
33578
+ warnIfDeprecatedRegion(options);
33579
+ if (!options.all && stacks.length === 0) {
33580
+ throw new Error("Stack name is required. Usage: cdkd drift <stack> [<stack>...] | --all");
33581
+ }
33582
+ await applyRoleArnIfSet({ roleArn: options.roleArn, region: options.region });
33583
+ const awsClients = new AwsClients({
33584
+ ...options.region && { region: options.region },
33585
+ ...options.profile && { profile: options.profile }
33586
+ });
33587
+ setAwsClients(awsClients);
33588
+ try {
33589
+ const region = options.region || process.env["AWS_REGION"] || "us-east-1";
33590
+ const bucket = await resolveStateBucketWithDefault(options.stateBucket, region);
33591
+ const prefix = options.statePrefix;
33592
+ const stateBackend = new S3StateBackend(
33593
+ awsClients.s3,
33594
+ { bucket, prefix },
33595
+ {
33596
+ region,
33597
+ ...options.profile && { profile: options.profile }
33598
+ }
33599
+ );
33600
+ await stateBackend.verifyBucketExists();
33601
+ const providerRegistry = new ProviderRegistry();
33602
+ registerAllProviders(providerRegistry);
33603
+ providerRegistry.setCustomResourceResponseBucket(bucket);
33604
+ const stateRefs = await stateBackend.listStacks();
33605
+ const targetRefs = resolveTargetRefs(stacks, stateRefs, options);
33606
+ const reports = [];
33607
+ for (const ref of targetRefs) {
33608
+ if (!ref.region) {
33609
+ throw new Error(
33610
+ `Stack '${ref.stackName}' has only a legacy state record without a region. Run 'cdkd deploy ${ref.stackName}' (or any cdkd write) to migrate it to the region-scoped layout, then re-run drift detection.`
33611
+ );
33612
+ }
33613
+ const report = await runDriftForStack(
33614
+ ref.stackName,
33615
+ ref.region,
33616
+ stateBackend,
33617
+ providerRegistry
33618
+ );
33619
+ reports.push(report);
33620
+ }
33621
+ if (options.json) {
33622
+ writeJsonReport(reports);
33623
+ } else {
33624
+ writeHumanReport(reports);
33625
+ }
33626
+ const drifted = reports.some((r) => r.outcomes.some((o) => o.kind === "drifted"));
33627
+ if (drifted) {
33628
+ throw new DriftDetectedError();
33629
+ }
33630
+ } finally {
33631
+ awsClients.destroy();
33632
+ }
33633
+ }
33634
+ function resolveTargetRefs(stacks, stateRefs, options) {
33635
+ if (options.all) {
33636
+ if (stateRefs.length === 0) {
33637
+ throw new Error("No stacks found in state bucket.");
33638
+ }
33639
+ if (options.stackRegion) {
33640
+ return stateRefs.filter((r) => r.region === options.stackRegion);
33641
+ }
33642
+ return stateRefs;
33643
+ }
33644
+ const out = [];
33645
+ for (const stackName of stacks) {
33646
+ const matches = stateRefs.filter((r) => r.stackName === stackName);
33647
+ if (matches.length === 0) {
33648
+ throw new Error(
33649
+ `No state found for stack '${stackName}'. Run 'cdkd state list' to see available stacks.`
33650
+ );
33651
+ }
33652
+ if (options.stackRegion) {
33653
+ const ref = matches.find((r) => r.region === options.stackRegion);
33654
+ if (!ref) {
33655
+ const seen = matches.map((r) => r.region ?? "(legacy)").join(", ");
33656
+ throw new Error(
33657
+ `No state found for stack '${stackName}' in region '${options.stackRegion}'. Available regions: ${seen}.`
33658
+ );
33659
+ }
33660
+ out.push(ref);
33661
+ continue;
33662
+ }
33663
+ if (matches.length === 1) {
33664
+ out.push(matches[0]);
33665
+ continue;
33666
+ }
33667
+ const regions = matches.map((r) => r.region ?? "(legacy)").join(", ");
33668
+ throw new Error(
33669
+ `Stack '${stackName}' has state in multiple regions: ${regions}. Re-run with --stack-region <region> to disambiguate.`
33670
+ );
33671
+ }
33672
+ return out;
33673
+ }
33674
+ async function runDriftForStack(stackName, region, stateBackend, providerRegistry) {
33675
+ const result = await stateBackend.getState(stackName, region);
33676
+ if (!result) {
33677
+ throw new Error(
33678
+ `No state found for stack '${stackName}' (${region}). Run 'cdkd state list' to see available stacks.`
33679
+ );
33680
+ }
33681
+ return await withStackName(stackName, async () => {
33682
+ const outcomes = [];
33683
+ const state = result.state;
33684
+ const entries = Object.entries(state.resources ?? {}).sort(([a], [b]) => a.localeCompare(b));
33685
+ for (const [logicalId, resource] of entries) {
33686
+ if (providerRegistry.shouldSkipResource(resource.resourceType)) {
33687
+ continue;
33688
+ }
33689
+ let provider;
33690
+ try {
33691
+ provider = providerRegistry.getProvider(resource.resourceType);
33692
+ } catch {
33693
+ outcomes.push({
33694
+ kind: "unsupported",
33695
+ logicalId,
33696
+ resourceType: resource.resourceType
33697
+ });
33698
+ continue;
33699
+ }
33700
+ if (!provider.readCurrentState) {
33701
+ outcomes.push({
33702
+ kind: "unsupported",
33703
+ logicalId,
33704
+ resourceType: resource.resourceType
33705
+ });
33706
+ continue;
33707
+ }
33708
+ const aws = await provider.readCurrentState(
33709
+ resource.physicalId,
33710
+ logicalId,
33711
+ resource.resourceType
33712
+ );
33713
+ if (aws === void 0) {
33714
+ outcomes.push({
33715
+ kind: "unsupported",
33716
+ logicalId,
33717
+ resourceType: resource.resourceType
33718
+ });
33719
+ continue;
33720
+ }
33721
+ const changes = calculateResourceDrift(resource.properties ?? {}, aws);
33722
+ if (changes.length === 0) {
33723
+ outcomes.push({ kind: "clean", logicalId, resourceType: resource.resourceType });
33724
+ } else {
33725
+ outcomes.push({
33726
+ kind: "drifted",
33727
+ logicalId,
33728
+ resourceType: resource.resourceType,
33729
+ changes
33730
+ });
33731
+ }
33732
+ }
33733
+ return { stackName, region, outcomes };
33734
+ });
33735
+ }
33736
+ function writeJsonReport(reports) {
33737
+ const payload = reports.map((r) => {
33738
+ const drifted = r.outcomes.filter((o) => o.kind === "drifted").map((o) => ({ logicalId: o.logicalId, type: o.resourceType, changes: o.changes }));
33739
+ const clean = r.outcomes.filter((o) => o.kind === "clean").map((o) => ({ logicalId: o.logicalId, type: o.resourceType }));
33740
+ const notSupported = r.outcomes.filter((o) => o.kind === "unsupported").map((o) => ({ logicalId: o.logicalId, type: o.resourceType }));
33741
+ return { stack: r.stackName, region: r.region, drifted, clean, notSupported };
33742
+ });
33743
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}
33744
+ `);
33745
+ }
33746
+ function writeHumanReport(reports) {
33747
+ for (const report of reports) {
33748
+ const drifted = report.outcomes.filter(
33749
+ (o) => o.kind === "drifted"
33750
+ );
33751
+ const unsupported = report.outcomes.filter(
33752
+ (o) => o.kind === "unsupported"
33753
+ );
33754
+ const total = report.outcomes.length;
33755
+ if (drifted.length === 0) {
33756
+ process.stdout.write(
33757
+ `\u2713 ${report.stackName} (${report.region}): no drift detected (${total} resource${total === 1 ? "" : "s"} checked, ${unsupported.length} unsupported)
33758
+ `
33759
+ );
33760
+ } else {
33761
+ const word = drifted.length === 1 ? "resource" : "resources";
33762
+ process.stdout.write(
33763
+ `
33764
+ \u26A0 ${report.stackName} (${report.region}): drift detected on ${drifted.length} ${word}
33765
+
33766
+ `
33767
+ );
33768
+ for (const o of drifted) {
33769
+ process.stdout.write(` ~ ${o.logicalId} (${o.resourceType})
33770
+ `);
33771
+ for (const change of o.changes) {
33772
+ process.stdout.write(` - ${change.path}: ${formatScalar(change.stateValue)}
33773
+ `);
33774
+ process.stdout.write(` + ${change.path}: ${formatScalar(change.awsValue)}
33775
+ `);
33776
+ }
33777
+ process.stdout.write("\n");
33778
+ }
33779
+ }
33780
+ if (unsupported.length > 0) {
33781
+ process.stdout.write(
33782
+ `
33783
+ ${unsupported.length} resource(s) reported as drift unknown \u2014 provider does not yet support drift detection:
33784
+ `
33785
+ );
33786
+ for (const o of unsupported) {
33787
+ process.stdout.write(` ? ${o.logicalId} (${o.resourceType})
33788
+ `);
33789
+ }
33790
+ }
33791
+ }
33792
+ }
33793
+ function formatScalar(value) {
33794
+ if (value === null)
33795
+ return "null";
33796
+ if (value === void 0)
33797
+ return "undefined";
33798
+ if (typeof value === "string")
33799
+ return value;
33800
+ if (typeof value === "number" || typeof value === "boolean")
33801
+ return String(value);
33802
+ return JSON.stringify(value);
33803
+ }
33804
+ function stackRegionOption() {
33805
+ return new Option3(
33806
+ "--stack-region <region>",
33807
+ "Region of the stack record to inspect. Required when the same stack name has state in multiple regions."
33808
+ );
33809
+ }
33810
+ function createDriftCommand() {
33811
+ const cmd = new Command6("drift").description(
33812
+ "Detect drift between cdkd state and AWS reality. Exits 0 when no drift, 1 when drift is detected."
33813
+ ).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));
33814
+ [...commonOptions, ...stateOptions].forEach((opt) => cmd.addOption(opt));
33815
+ cmd.addOption(deprecatedRegionOption);
33816
+ return cmd;
33817
+ }
33818
+
33459
33819
  // src/cli/commands/destroy.ts
33460
- import { Command as Command6 } from "commander";
33820
+ import { Command as Command7 } from "commander";
33461
33821
  init_aws_clients();
33462
33822
 
33463
33823
  // src/cli/commands/destroy-runner.ts
@@ -33880,7 +34240,7 @@ Preparing to destroy stack: ${stackName}`);
33880
34240
  }
33881
34241
  }
33882
34242
  function createDestroyCommand() {
33883
- const cmd = new Command6("destroy").description("Destroy all resources in the stack").argument(
34243
+ const cmd = new Command7("destroy").description("Destroy all resources in the stack").argument(
33884
34244
  "[stacks...]",
33885
34245
  "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/*')."
33886
34246
  ).option("--all", "Destroy all stacks", false).action(withErrorHandling(destroyCommand));
@@ -33899,7 +34259,7 @@ function createDestroyCommand() {
33899
34259
 
33900
34260
  // src/cli/commands/orphan.ts
33901
34261
  import * as readline2 from "node:readline/promises";
33902
- import { Command as Command7 } from "commander";
34262
+ import { Command as Command8 } from "commander";
33903
34263
  init_aws_clients();
33904
34264
 
33905
34265
  // src/cli/cdk-path.ts
@@ -34580,7 +34940,7 @@ async function confirmPrompt(prompt) {
34580
34940
  }
34581
34941
  }
34582
34942
  function createOrphanCommand() {
34583
- const cmd = new Command7("orphan").description(
34943
+ const cmd = new Command8("orphan").description(
34584
34944
  "Remove one or more resources from cdkd state by construct path (does NOT delete AWS resources). Mirrors aws-cdk-cli's 'cdk orphan --unstable=orphan'. Synth-driven; for the previous whole-stack-orphan behavior, use 'cdkd state orphan <stack>'."
34585
34945
  ).argument(
34586
34946
  "<paths...>",
@@ -34606,7 +34966,7 @@ function createOrphanCommand() {
34606
34966
  }
34607
34967
 
34608
34968
  // src/cli/commands/publish-assets.ts
34609
- import { Option as Option3, Command as Command8 } from "commander";
34969
+ import { Option as Option4, Command as Command9 } from "commander";
34610
34970
  async function publishAssetsCommand(options) {
34611
34971
  const logger = getLogger();
34612
34972
  if (options.verbose) {
@@ -34626,13 +34986,13 @@ async function publishAssetsCommand(options) {
34626
34986
  logger.info("\u2705 Asset publishing complete");
34627
34987
  }
34628
34988
  function createPublishAssetsCommand() {
34629
- const cmd = new Command8("publish-assets").description("Publish assets to S3/ECR from asset manifest").requiredOption("--path <path>", "Path to asset manifest file or directory").addOption(
34630
- new Option3(
34989
+ const cmd = new Command9("publish-assets").description("Publish assets to S3/ECR from asset manifest").requiredOption("--path <path>", "Path to asset manifest file or directory").addOption(
34990
+ new Option4(
34631
34991
  "--asset-publish-concurrency <number>",
34632
34992
  "Maximum concurrent asset publish operations"
34633
34993
  ).default(8).argParser((value) => parseInt(value, 10))
34634
34994
  ).addOption(
34635
- new Option3("--image-build-concurrency <number>", "Maximum concurrent Docker image builds").default(4).argParser((value) => parseInt(value, 10))
34995
+ new Option4("--image-build-concurrency <number>", "Maximum concurrent Docker image builds").default(4).argParser((value) => parseInt(value, 10))
34636
34996
  ).action(withErrorHandling(publishAssetsCommand));
34637
34997
  commonOptions.forEach((opt) => cmd.addOption(opt));
34638
34998
  cmd.addOption(deprecatedRegionOption);
@@ -34640,7 +35000,7 @@ function createPublishAssetsCommand() {
34640
35000
  }
34641
35001
 
34642
35002
  // src/cli/commands/force-unlock.ts
34643
- import { Command as Command9, Option as Option4 } from "commander";
35003
+ import { Command as Command10, Option as Option5 } from "commander";
34644
35004
  init_aws_clients();
34645
35005
  async function forceUnlockCommand(stackArgs, options) {
34646
35006
  const logger = getLogger();
@@ -34701,8 +35061,8 @@ async function forceUnlockCommand(stackArgs, options) {
34701
35061
  }
34702
35062
  }
34703
35063
  function createForceUnlockCommand() {
34704
- const cmd = new Command9("force-unlock").description("Force-release a stale lock on a stack").argument("[stacks...]", "Stack name(s) to unlock").addOption(
34705
- new Option4(
35064
+ const cmd = new Command10("force-unlock").description("Force-release a stale lock on a stack").argument("[stacks...]", "Stack name(s) to unlock").addOption(
35065
+ new Option5(
34706
35066
  "--stack-region <region>",
34707
35067
  "Stack region whose lock to release (use when the same stack name has locks in multiple regions). Defaults to all regions where the stack has state."
34708
35068
  )
@@ -34714,7 +35074,7 @@ function createForceUnlockCommand() {
34714
35074
 
34715
35075
  // src/cli/commands/state.ts
34716
35076
  import * as readline4 from "node:readline/promises";
34717
- import { Command as Command11, Option as Option5 } from "commander";
35077
+ import { Command as Command12, Option as Option6 } from "commander";
34718
35078
  import {
34719
35079
  GetBucketLocationCommand as GetBucketLocationCommand2,
34720
35080
  GetObjectCommand as GetObjectCommand4,
@@ -34724,7 +35084,7 @@ init_aws_clients();
34724
35084
 
34725
35085
  // src/cli/commands/state-migrate.ts
34726
35086
  import * as readline3 from "node:readline/promises";
34727
- import { Command as Command10 } from "commander";
35087
+ import { Command as Command11 } from "commander";
34728
35088
  import {
34729
35089
  CopyObjectCommand,
34730
35090
  CreateBucketCommand as CreateBucketCommand4,
@@ -35008,7 +35368,7 @@ async function confirmPrompt2(prompt) {
35008
35368
  }
35009
35369
  }
35010
35370
  function createStateMigrateCommand() {
35011
- const cmd = new Command10("migrate").description(
35371
+ const cmd = new Command11("migrate").description(
35012
35372
  "Migrate state from the legacy region-suffixed bucket (cdkd-state-{account}-{region}) to the new region-free default (cdkd-state-{account}). Source bucket is kept by default; pass --remove-legacy to delete it after a successful migration."
35013
35373
  ).option(
35014
35374
  "--region <region>",
@@ -35167,7 +35527,7 @@ async function stateListCommand(options) {
35167
35527
  }
35168
35528
  }
35169
35529
  function createStateListCommand() {
35170
- const cmd = new Command11("list").alias("ls").description("List stacks registered in the cdkd state bucket").option("-l, --long", "Show resource count, last-modified time, and lock status", false).option("--json", "Output as JSON", false).action(withErrorHandling(stateListCommand));
35530
+ const cmd = new Command12("list").alias("ls").description("List stacks registered in the cdkd state bucket").option("-l, --long", "Show resource count, last-modified time, and lock status", false).option("--json", "Output as JSON", false).action(withErrorHandling(stateListCommand));
35171
35531
  [...commonOptions, ...stateOptions].forEach((opt) => cmd.addOption(opt));
35172
35532
  cmd.addOption(deprecatedRegionOption);
35173
35533
  return cmd;
@@ -35271,7 +35631,7 @@ function formatLockSummary(lockInfo) {
35271
35631
  return `locked by ${lockInfo.owner}${opStr}, ${expiresStr}`;
35272
35632
  }
35273
35633
  function createStateResourcesCommand() {
35274
- const cmd = new Command11("resources").description("List resources recorded in a stack's state").argument("<stack>", "Stack name (physical CloudFormation name)").option("-l, --long", "Include dependencies and attributes per resource", false).option("--json", "Output as JSON", false).addOption(stackRegionOption()).action(withErrorHandling(stateResourcesCommand));
35634
+ const cmd = new Command12("resources").description("List resources recorded in a stack's state").argument("<stack>", "Stack name (physical CloudFormation name)").option("-l, --long", "Include dependencies and attributes per resource", false).option("--json", "Output as JSON", false).addOption(stackRegionOption2()).action(withErrorHandling(stateResourcesCommand));
35275
35635
  [...commonOptions, ...stateOptions].forEach((opt) => cmd.addOption(opt));
35276
35636
  cmd.addOption(deprecatedRegionOption);
35277
35637
  return cmd;
@@ -35359,7 +35719,7 @@ async function stateShowCommand(stackName, options) {
35359
35719
  }
35360
35720
  }
35361
35721
  function createStateShowCommand() {
35362
- const cmd = new Command11("show").description("Show the full cdkd state record for a stack (metadata, outputs, resources)").argument("<stack>", "Stack name (physical CloudFormation name)").option("--json", "Output the raw state and lock as JSON", false).addOption(stackRegionOption()).action(withErrorHandling(stateShowCommand));
35722
+ const cmd = new Command12("show").description("Show the full cdkd state record for a stack (metadata, outputs, resources)").argument("<stack>", "Stack name (physical CloudFormation name)").option("--json", "Output the raw state and lock as JSON", false).addOption(stackRegionOption2()).action(withErrorHandling(stateShowCommand));
35363
35723
  [...commonOptions, ...stateOptions].forEach((opt) => cmd.addOption(opt));
35364
35724
  cmd.addOption(deprecatedRegionOption);
35365
35725
  return cmd;
@@ -35435,16 +35795,16 @@ Use 'cdkd destroy ${stackName}' if you want to delete the actual resources.
35435
35795
  setup.dispose();
35436
35796
  }
35437
35797
  }
35438
- function stackRegionOption() {
35439
- return new Option5(
35798
+ function stackRegionOption2() {
35799
+ return new Option6(
35440
35800
  "--stack-region <region>",
35441
35801
  "Region of the stack record to operate on. Required when the same stack name has state in multiple regions."
35442
35802
  );
35443
35803
  }
35444
35804
  function createStateOrphanCommand() {
35445
- const cmd = new Command11("orphan").description(
35805
+ const cmd = new Command12("orphan").description(
35446
35806
  "Orphan one or more stacks from cdkd state (removes the state record; does NOT delete AWS resources)"
35447
- ).argument("<stacks...>", "Stack name(s) to orphan from state").option("-f, --force", "Skip confirmation and remove even if the stack is locked", false).addOption(stackRegionOption()).action(withErrorHandling(stateOrphanCommand));
35807
+ ).argument("<stacks...>", "Stack name(s) to orphan from state").option("-f, --force", "Skip confirmation and remove even if the stack is locked", false).addOption(stackRegionOption2()).action(withErrorHandling(stateOrphanCommand));
35448
35808
  [...commonOptions, ...stateOptions].forEach((opt) => cmd.addOption(opt));
35449
35809
  cmd.addOption(deprecatedRegionOption);
35450
35810
  return cmd;
@@ -35585,9 +35945,9 @@ Preparing to destroy stack: ${stackName}${ref.region ? ` (${ref.region})` : ""}`
35585
35945
  }
35586
35946
  }
35587
35947
  function createStateDestroyCommand() {
35588
- const cmd = new Command11("destroy").description(
35948
+ const cmd = new Command12("destroy").description(
35589
35949
  "Destroy a stack's AWS resources and remove its state record without requiring the CDK app. For removing only the state record (keeping AWS resources intact), use 'cdkd state orphan'."
35590
- ).argument("[stacks...]", "Stack name(s) to destroy (physical CloudFormation names)").option("--all", "Destroy every stack in the state bucket", false).addOption(stackRegionOption()).addHelpText(
35950
+ ).argument("[stacks...]", "Stack name(s) to destroy (physical CloudFormation names)").option("--all", "Destroy every stack in the state bucket", false).addOption(stackRegionOption2()).addHelpText(
35591
35951
  "after",
35592
35952
  [
35593
35953
  "",
@@ -35720,14 +36080,14 @@ async function stateInfoCommand(options) {
35720
36080
  }
35721
36081
  }
35722
36082
  function createStateInfoCommand() {
35723
- const cmd = new Command11("info").description(
36083
+ const cmd = new Command12("info").description(
35724
36084
  "Show cdkd state bucket info (bucket name, region, source, schema version, stack count)"
35725
36085
  ).option("--json", "Output as JSON", false).action(withErrorHandling(stateInfoCommand));
35726
36086
  [...commonOptions, ...stateOptions].forEach((opt) => cmd.addOption(opt));
35727
36087
  return cmd;
35728
36088
  }
35729
36089
  function createStateCommand() {
35730
- const cmd = new Command11("state").description("Manage cdkd state stored in S3");
36090
+ const cmd = new Command12("state").description("Manage cdkd state stored in S3");
35731
36091
  cmd.addCommand(createStateInfoCommand());
35732
36092
  cmd.addCommand(createStateListCommand());
35733
36093
  cmd.addCommand(createStateResourcesCommand());
@@ -35741,7 +36101,7 @@ function createStateCommand() {
35741
36101
  // src/cli/commands/import.ts
35742
36102
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
35743
36103
  import * as readline6 from "node:readline/promises";
35744
- import { Command as Command12 } from "commander";
36104
+ import { Command as Command13 } from "commander";
35745
36105
  init_aws_clients();
35746
36106
 
35747
36107
  // src/cli/commands/retire-cfn-stack.ts
@@ -36421,7 +36781,7 @@ async function confirmPrompt4(prompt) {
36421
36781
  }
36422
36782
  }
36423
36783
  function createImportCommand() {
36424
- const cmd = new Command12("import").description(
36784
+ const cmd = new Command13("import").description(
36425
36785
  "Adopt already-deployed AWS resources into cdkd state. Reads the CDK app to find logical IDs, resource types, and dependencies. With no flags, imports every resource via the aws:cdk:path tag. With --resource / --resource-mapping, only the listed resources are imported (CDK CLI parity); pass --auto to also tag-import the rest."
36426
36786
  ).argument(
36427
36787
  "[stack]",
@@ -36469,6 +36829,7 @@ var SUBCOMMANDS = /* @__PURE__ */ new Set([
36469
36829
  "ls",
36470
36830
  "deploy",
36471
36831
  "diff",
36832
+ "drift",
36472
36833
  "destroy",
36473
36834
  "orphan",
36474
36835
  "import",
@@ -36487,13 +36848,14 @@ function reorderArgs(argv) {
36487
36848
  return [...prefix, ...cmdAndAfter, ...beforeCmd];
36488
36849
  }
36489
36850
  async function main() {
36490
- const program = new Command13();
36491
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.34.0");
36851
+ const program = new Command14();
36852
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.35.0");
36492
36853
  program.addCommand(createBootstrapCommand());
36493
36854
  program.addCommand(createSynthCommand());
36494
36855
  program.addCommand(createListCommand());
36495
36856
  program.addCommand(createDeployCommand());
36496
36857
  program.addCommand(createDiffCommand());
36858
+ program.addCommand(createDriftCommand());
36497
36859
  program.addCommand(createDestroyCommand());
36498
36860
  program.addCommand(createOrphanCommand());
36499
36861
  program.addCommand(createImportCommand());