@go-to-k/cdkd 0.46.1 → 0.47.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/dist/cli.js CHANGED
@@ -629,6 +629,10 @@ var deployOptions = [
629
629
  new Option("--dry-run", "Show changes without applying").default(false),
630
630
  new Option("--skip-assets", "Skip asset publishing").default(false),
631
631
  new Option("--no-rollback", "Skip rollback on deployment failure"),
632
+ new Option(
633
+ "--no-capture-observed-state",
634
+ "Skip capturing AWS-current properties after each create/update (adds a fire-and-forget readCurrentState per resource so cdkd drift can compare against the real deploy-time AWS snapshot instead of the template). On by default. Disable when deploy speed matters more than rich drift detection \u2014 falls back to comparing against template properties (the pre-v3 behavior)."
635
+ ),
632
636
  noWaitOption,
633
637
  aggressiveVpcParallelOption,
634
638
  new Option(
@@ -1351,6 +1355,16 @@ function resolveApp(cliApp) {
1351
1355
  const cdkJson = loadCdkJson();
1352
1356
  return cdkJson?.app ?? void 0;
1353
1357
  }
1358
+ function resolveCaptureObservedState(cliValue) {
1359
+ if (cliValue === false)
1360
+ return false;
1361
+ const cdkJson = loadCdkJson();
1362
+ const cdkdContext = cdkJson?.context?.["cdkd"];
1363
+ const v = cdkdContext?.["captureObservedState"];
1364
+ if (typeof v === "boolean")
1365
+ return v;
1366
+ return true;
1367
+ }
1354
1368
  function resolveStateBucketWithSource(cliBucket) {
1355
1369
  if (cliBucket)
1356
1370
  return { bucket: cliBucket, source: "cli-flag" };
@@ -3727,8 +3741,8 @@ import {
3727
3741
  } from "@aws-sdk/client-s3";
3728
3742
 
3729
3743
  // src/types/state.ts
3730
- var STATE_SCHEMA_VERSION_LEGACY = 1;
3731
- var STATE_SCHEMA_VERSION_CURRENT = 2;
3744
+ var STATE_SCHEMA_VERSION_CURRENT = 3;
3745
+ var STATE_SCHEMA_VERSIONS_READABLE = [1, 2, 3];
3732
3746
 
3733
3747
  // src/utils/aws-region-resolver.ts
3734
3748
  import { GetBucketLocationCommand, S3Client as S3Client3 } from "@aws-sdk/client-s3";
@@ -4237,9 +4251,9 @@ var S3StateBackend = class {
4237
4251
  );
4238
4252
  }
4239
4253
  const v = parsed.version;
4240
- if (v !== STATE_SCHEMA_VERSION_LEGACY && v !== STATE_SCHEMA_VERSION_CURRENT && v !== void 0) {
4254
+ if (v !== void 0 && !STATE_SCHEMA_VERSIONS_READABLE.includes(v)) {
4241
4255
  throw new StateError(
4242
- `Unsupported state schema version ${String(v)} for stack '${stackName}'. This cdkd binary supports versions ${String(STATE_SCHEMA_VERSION_LEGACY)} and ${String(STATE_SCHEMA_VERSION_CURRENT)}. Upgrade cdkd to a version that supports schema ${String(v)}.`
4256
+ `Unsupported state schema version ${String(v)} for stack '${stackName}'. This cdkd binary supports versions ${STATE_SCHEMA_VERSIONS_READABLE.join(", ")}. Upgrade cdkd to a version that supports schema ${String(v)}.`
4243
4257
  );
4244
4258
  }
4245
4259
  return parsed;
@@ -36947,10 +36961,23 @@ var DeployEngine = class {
36947
36961
  this.options.noRollback = options.noRollback ?? false;
36948
36962
  this.options.resourceWarnAfterMs = options.resourceWarnAfterMs ?? DEFAULT_RESOURCE_WARN_AFTER_MS;
36949
36963
  this.options.resourceTimeoutMs = options.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;
36964
+ this.options.captureObservedState = options.captureObservedState ?? true;
36950
36965
  }
36951
36966
  logger = getLogger().child("DeployEngine");
36952
36967
  resolver;
36953
36968
  interrupted = false;
36969
+ /**
36970
+ * In-flight `provider.readCurrentState` promises kicked off after a
36971
+ * successful CREATE / UPDATE. The deploy critical path does NOT
36972
+ * `await` these; instead they're drained at the end of `doDeploy`
36973
+ * (success path only) and the resolved values are merged into
36974
+ * `ResourceState.observedProperties` before the final state save.
36975
+ *
36976
+ * Each Promise resolves to the AWS-current snapshot, or `undefined`
36977
+ * if the provider does not implement `readCurrentState` or the call
36978
+ * threw — never rejects, so an unhandled-rejection cannot escape.
36979
+ */
36980
+ observedCaptureTasks = /* @__PURE__ */ new Map();
36954
36981
  /**
36955
36982
  * Target region for this stack. Required — load-bearing for the
36956
36983
  * region-prefixed S3 state key and recorded in state.json for
@@ -36963,6 +36990,61 @@ var DeployEngine = class {
36963
36990
  async deploy(stackName, template) {
36964
36991
  return withStackName(stackName, () => this.doDeploy(stackName, template));
36965
36992
  }
36993
+ /**
36994
+ * Kick off `provider.readCurrentState` for a freshly-created/updated
36995
+ * resource without blocking the deploy critical path. The promise
36996
+ * lands in `observedCaptureTasks` keyed by `logicalId`; the deploy's
36997
+ * success-path drain (`drainObservedCaptures`) awaits the full set
36998
+ * and merges the resolved values into `ResourceState.observedProperties`
36999
+ * before the final state save.
37000
+ *
37001
+ * Errors are swallowed at the Promise level — readCurrentState
37002
+ * failing must not fail the deploy. The map entry resolves to
37003
+ * `undefined` for failures and for providers without
37004
+ * `readCurrentState`; both translate to "no observedProperties" at
37005
+ * the merge step, which is fine: drift falls back to comparing
37006
+ * against `properties`.
37007
+ */
37008
+ kickOffObservedCapture(provider, logicalId, physicalId, resourceType, resolvedProps) {
37009
+ if (this.options.captureObservedState !== true)
37010
+ return;
37011
+ if (!provider.readCurrentState)
37012
+ return;
37013
+ const promise = provider.readCurrentState(physicalId, logicalId, resourceType, resolvedProps).catch((err) => {
37014
+ this.logger.debug(
37015
+ `observedProperties capture for ${logicalId} (${resourceType}) failed: ${err instanceof Error ? err.message : String(err)} \u2014 drift will fall back to template properties for this resource until the next successful deploy.`
37016
+ );
37017
+ return void 0;
37018
+ });
37019
+ this.observedCaptureTasks.set(logicalId, promise);
37020
+ }
37021
+ /**
37022
+ * Wait for every in-flight `readCurrentState` promise from the
37023
+ * deploy's success path, then merge each resolved snapshot into the
37024
+ * matching `ResourceState.observedProperties`. After this runs the
37025
+ * map is drained so a subsequent deploy starts fresh.
37026
+ *
37027
+ * Called from `doDeploy` immediately before the final `saveState`.
37028
+ * The rollback / failure paths intentionally do NOT call this — a
37029
+ * failed deploy's partial state is already inconsistent, and waiting
37030
+ * on potentially many in-flight reads would slow down the rollback
37031
+ * itself.
37032
+ */
37033
+ async drainObservedCaptures(stateResources) {
37034
+ if (this.observedCaptureTasks.size === 0)
37035
+ return;
37036
+ const entries = Array.from(this.observedCaptureTasks.entries());
37037
+ this.observedCaptureTasks.clear();
37038
+ const resolved = await Promise.all(entries.map(([, p]) => p));
37039
+ for (let i = 0; i < entries.length; i++) {
37040
+ const logicalId = entries[i][0];
37041
+ const observed = resolved[i];
37042
+ const target = stateResources[logicalId];
37043
+ if (target && observed !== void 0) {
37044
+ target.observedProperties = observed;
37045
+ }
37046
+ }
37047
+ }
36966
37048
  async doDeploy(stackName, template) {
36967
37049
  const startTime = Date.now();
36968
37050
  this.logger.debug(`Starting deployment for stack: ${stackName}`);
@@ -37079,6 +37161,7 @@ var DeployEngine = class {
37079
37161
  progress,
37080
37162
  migrationPending
37081
37163
  );
37164
+ await this.drainObservedCaptures(newState.resources);
37082
37165
  const newEtag = await this.stateBackend.saveState(stackName, this.stackRegion, newState);
37083
37166
  this.logger.debug(`State saved (ETag: ${newEtag})`);
37084
37167
  const durationMs = Date.now() - startTime;
@@ -37094,6 +37177,7 @@ var DeployEngine = class {
37094
37177
  } finally {
37095
37178
  renderer.stop();
37096
37179
  process.removeListener("SIGINT", sigintHandler);
37180
+ this.observedCaptureTasks.clear();
37097
37181
  try {
37098
37182
  await this.lockManager.releaseLock(stackName, this.stackRegion);
37099
37183
  this.logger.debug("Lock released");
@@ -37647,6 +37731,13 @@ var DeployEngine = class {
37647
37731
  ...result.attributes && { attributes: result.attributes },
37648
37732
  ...dependencies && dependencies.length > 0 && { dependencies }
37649
37733
  };
37734
+ this.kickOffObservedCapture(
37735
+ provider,
37736
+ logicalId,
37737
+ result.physicalId,
37738
+ resourceType,
37739
+ resolvedProps
37740
+ );
37650
37741
  if (counts)
37651
37742
  counts.created++;
37652
37743
  if (progress)
@@ -37725,6 +37816,13 @@ var DeployEngine = class {
37725
37816
  ...createResult.attributes && { attributes: createResult.attributes },
37726
37817
  ...dependencies && dependencies.length > 0 && { dependencies }
37727
37818
  };
37819
+ this.kickOffObservedCapture(
37820
+ provider,
37821
+ logicalId,
37822
+ createResult.physicalId,
37823
+ resourceType,
37824
+ resolvedProps
37825
+ );
37728
37826
  if (counts)
37729
37827
  counts.updated++;
37730
37828
  if (progress)
@@ -37803,6 +37901,13 @@ var DeployEngine = class {
37803
37901
  ...result.attributes && { attributes: result.attributes },
37804
37902
  ...dependencies && dependencies.length > 0 && { dependencies }
37805
37903
  };
37904
+ this.kickOffObservedCapture(
37905
+ provider,
37906
+ logicalId,
37907
+ result.physicalId,
37908
+ resourceType,
37909
+ resolvedProps
37910
+ );
37806
37911
  if (counts)
37807
37912
  counts.updated++;
37808
37913
  if (progress)
@@ -38281,6 +38386,7 @@ Deploying stack: ${stackInfo.stackName}${stackRegion !== baseRegion ? ` (region:
38281
38386
  concurrency: options.concurrency,
38282
38387
  dryRun: options.dryRun,
38283
38388
  noRollback: !options.rollback,
38389
+ captureObservedState: resolveCaptureObservedState(options.captureObservedState),
38284
38390
  ...options.resourceWarnAfter?.globalMs !== void 0 && {
38285
38391
  resourceWarnAfterMs: options.resourceWarnAfter.globalMs
38286
38392
  },
@@ -38984,7 +39090,8 @@ async function runDriftForStack(stackName, region, stateBackend, providerRegistr
38984
39090
  continue;
38985
39091
  }
38986
39092
  const ignorePaths = provider.getDriftUnknownPaths ? provider.getDriftUnknownPaths(resource.resourceType) : [];
38987
- const changes = calculateResourceDrift(resource.properties ?? {}, aws, { ignorePaths });
39093
+ const baseline = resource.observedProperties ?? resource.properties ?? {};
39094
+ const changes = calculateResourceDrift(baseline, aws, { ignorePaths });
38988
39095
  if (changes.length === 0) {
38989
39096
  outcomes.push({ kind: "clean", logicalId, resourceType: resource.resourceType });
38990
39097
  } else {
@@ -39056,14 +39163,13 @@ async function runAccept(reports, stateBackend, stateConfig, awsClients, options
39056
39163
  const existing = resources[outcome.logicalId];
39057
39164
  if (!existing)
39058
39165
  continue;
39059
- const newProperties = JSON.parse(JSON.stringify(existing.properties ?? {}));
39166
+ const hasObserved = existing.observedProperties !== void 0;
39167
+ const baselineSource = hasObserved ? existing.observedProperties : existing.properties ?? {};
39168
+ const newBaseline = JSON.parse(JSON.stringify(baselineSource));
39060
39169
  for (const change of outcome.changes) {
39061
- setAtPath(newProperties, change.path, change.awsValue);
39170
+ setAtPath(newBaseline, change.path, change.awsValue);
39062
39171
  }
39063
- resources[outcome.logicalId] = {
39064
- ...existing,
39065
- properties: newProperties
39066
- };
39172
+ resources[outcome.logicalId] = hasObserved ? { ...existing, observedProperties: newBaseline } : { ...existing, properties: newBaseline };
39067
39173
  }
39068
39174
  const newState = {
39069
39175
  ...report.state,
@@ -39130,13 +39236,14 @@ async function runRevert(reports, providerRegistry, stateConfig, awsClients, opt
39130
39236
  return;
39131
39237
  }
39132
39238
  const provider = providerRegistry.getProvider(outcome.resourceType);
39239
+ const desiredProperties = stateResource.observedProperties ?? stateResource.properties ?? {};
39133
39240
  try {
39134
39241
  await withRetry(
39135
39242
  () => provider.update(
39136
39243
  outcome.logicalId,
39137
39244
  stateResource.physicalId,
39138
39245
  outcome.resourceType,
39139
- stateResource.properties ?? {},
39246
+ desiredProperties,
39140
39247
  outcome.awsProperties
39141
39248
  ),
39142
39249
  outcome.logicalId,
@@ -42209,6 +42316,7 @@ async function importCommand(stackArg, options) {
42209
42316
  existingState,
42210
42317
  selectiveMode
42211
42318
  );
42319
+ await captureObservedForImportedResources(stackState, providerRegistry, logger);
42212
42320
  const saveOptions = {};
42213
42321
  if (existingEtag) {
42214
42322
  saveOptions.expectedEtag = existingEtag;
@@ -42409,7 +42517,7 @@ function buildStackState(stackName, region, rows, templateParser, template, exis
42409
42517
  };
42410
42518
  }
42411
42519
  return {
42412
- version: 2,
42520
+ version: STATE_SCHEMA_VERSION_CURRENT,
42413
42521
  stackName,
42414
42522
  region,
42415
42523
  resources,
@@ -42502,6 +42610,33 @@ function createImportCommand() {
42502
42610
  function collectMultiple(value, previous) {
42503
42611
  return [...previous ?? [], value];
42504
42612
  }
42613
+ async function captureObservedForImportedResources(stackState, providerRegistry, logger) {
42614
+ const entries = Object.entries(stackState.resources);
42615
+ if (entries.length === 0)
42616
+ return;
42617
+ await Promise.all(
42618
+ entries.map(async ([logicalId, resource]) => {
42619
+ try {
42620
+ const provider = providerRegistry.getProvider(resource.resourceType);
42621
+ if (!provider.readCurrentState)
42622
+ return;
42623
+ const observed = await provider.readCurrentState(
42624
+ resource.physicalId,
42625
+ logicalId,
42626
+ resource.resourceType,
42627
+ resource.properties ?? {}
42628
+ );
42629
+ if (observed !== void 0) {
42630
+ resource.observedProperties = observed;
42631
+ }
42632
+ } catch (err) {
42633
+ logger.debug(
42634
+ `observedProperties capture for imported ${logicalId} (${resource.resourceType}) failed: ${err instanceof Error ? err.message : String(err)} \u2014 drift will fall back to template properties for this resource until the next successful deploy.`
42635
+ );
42636
+ }
42637
+ })
42638
+ );
42639
+ }
42505
42640
 
42506
42641
  // src/cli/index.ts
42507
42642
  var SUBCOMMANDS = /* @__PURE__ */ new Set([
@@ -42531,7 +42666,7 @@ function reorderArgs(argv) {
42531
42666
  }
42532
42667
  async function main() {
42533
42668
  const program = new Command14();
42534
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.46.1");
42669
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.47.0");
42535
42670
  program.addCommand(createBootstrapCommand());
42536
42671
  program.addCommand(createSynthCommand());
42537
42672
  program.addCommand(createListCommand());