@go-to-k/cdkd 0.280.28 → 0.280.29

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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { $ as requireConfigObject, $t as AssemblyReader, A as green, At as runDockerForeground, B as disableInstanceApiTermination, Bt as resolveCaptureObservedState, C as MULTI_REGION_RECREATE_BLOCKED_TYPES, Cn as StackTerminationProtectionError, Ct as getBootstrapMarkerKey, D as bold, Dt as buildDockerImage, E as formatResourceLine, Et as validateContainerRepoName, F as clearOnUpdateRemoval, Ft as synthesisStatusMessage, G as WAFv2WebACLProvider, Gt as stateBucketExistenceConfirmed, H as IntrinsicFunctionResolver, Ht as resolveStateBucketWithDefault, I as ProviderRegistry, It as getDefaultStateBucketName, J as assertRegionMatch, Jt as CFN_TEMPLATE_URL_LIMIT, K as normalizeAwsTagsToCfn, Kt as warnDeprecatedNoPrefixCliFlag, L as findActionableSilentDrops, Lt as getLegacyStateBucketName, M as yellow, Mt as AssetManifestLoader, N as IAMRoleProvider, Nt as getDockerImageBySourceHash, O as cyan, On as normalizeAwsError, Ot as formatDockerLoginError, P as collectInlinePolicyNamesManagedBySiblings, Pt as Synthesizer, Q as requireConfigArray, Qt as expectedOwnerParam, R as CloudControlProvider, Rt as resolveApp, S as extractDeploymentEventError, Sn as StackHasActiveImportsError, St as ensureAssetStorage, T as renderStatefulReason, Tt as validateAssetBucketName, U as cfnRefValueFromPhysicalId, Ut as resolveStateBucketWithDefaultAndSource, V as isTerminationProtectionPropagationError, Vt as resolveSkipPrefix, W as refStateLookupFromResource, Wt as resolveUseCdkBootstrapAssets, X as readConfigString, Xt as findLargeInlineResources, Y as configStringRefusal, Yt as MIGRATE_TMP_PREFIX, Z as replayWarn, Zt as uploadCfnTemplate, _ as createPreDeleteFinalSnapshot, _n as NestedStackChildDirectDestroyError, _t as createAssetRedirectResolver, a as DeploymentEventsStore, an as resetAwsClients, at as isRetryableTransientError, b as unsupportedFinalSnapshotError, bn as ResourceTimeoutError, bt as AssetModeResolver, c as replayFailedOperations, cn as CdkdError, ct as TemplateParser, d as IMPLICIT_DELETE_DEPENDENCIES, dn as DeployCancelledError, dt as rebuildClientForBucketRegion, en as processStackMessages, et as requireConfigString, f as computeImplicitDeleteEdges, fn as LocalInvokeBuildError$1, ft as shouldRetainResource, g as ccRoutedFinalSnapshotError, gn as MissingCdkCliError, gt as buildAssetRedirectMap, h as buildFinalSnapshotIdentifier, ht as WorkGraph, i as DeploymentEventsReader, in as getAwsClients, it as withRetry, j as red, jt as runDockerStreaming, k as gray, kn as withErrorHandling, kt as getDockerCmd, l as replayRollback, lt as LockManager, m as PRE_DELETE_SNAPSHOT_TYPES, mn as LocalStartServiceError, mt as stringifyValue, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as resolveBucketRegion, nt as DiffCalculator, o as planFailedOps, on as setAwsClients, ot as isThrottlingError, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as LocalMigrateError, pt as AssetPublisher, q as resolveExplicitPhysicalId, qt as CFN_TEMPLATE_BODY_LIMIT, r as DeployEngine, rn as AwsClients, rt as describeTypeWithThrottleRetry, s as planRollback, st as DagBuilder, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as applyRoleArnIfSet, u as withResourceDeadline, ut as S3StateBackend, v as isFinalSnapshotError, vn as PartialFailureError, vt as loadPublishableAssetManifest, w as isStatefulRecreateTargetSync, wt as parseBootstrapMarker, x as makeCanonicalizePropertiesFn, xn as ResourceUpdateNotSupportedError, xt as BOOTSTRAP_MARKER_PREFIX, y as refusesFinalSnapshot, yn as ProvisioningError, yt as rewriteTemplateAssetReferences, z as slowCcOperationTimeoutMs, zt as resolveAutoAssetStorage } from "./deploy-engine-Dkep5i7x.js";
2
+ import { $ as requireConfigObject, $t as AssemblyReader, A as green, At as runDockerForeground, B as disableInstanceApiTermination, Bt as resolveCaptureObservedState, C as MULTI_REGION_RECREATE_BLOCKED_TYPES, Cn as StackTerminationProtectionError, Ct as getBootstrapMarkerKey, D as bold, Dt as buildDockerImage, E as formatResourceLine, Et as validateContainerRepoName, F as clearOnUpdateRemoval, Ft as synthesisStatusMessage, G as WAFv2WebACLProvider, Gt as stateBucketExistenceConfirmed, H as IntrinsicFunctionResolver, Ht as resolveStateBucketWithDefault, I as ProviderRegistry, It as getDefaultStateBucketName, J as assertRegionMatch, Jt as CFN_TEMPLATE_URL_LIMIT, K as normalizeAwsTagsToCfn, Kt as warnDeprecatedNoPrefixCliFlag, L as findActionableSilentDrops, Lt as getLegacyStateBucketName, M as yellow, Mt as AssetManifestLoader, N as IAMRoleProvider, Nt as getDockerImageBySourceHash, O as cyan, On as normalizeAwsError, Ot as formatDockerLoginError, P as collectInlinePolicyNamesManagedBySiblings, Pt as Synthesizer, Q as requireConfigArray, Qt as expectedOwnerParam, R as CloudControlProvider, Rt as resolveApp, S as extractDeploymentEventError, Sn as StackHasActiveImportsError, St as ensureAssetStorage, T as renderStatefulReason, Tt as validateAssetBucketName, U as cfnRefValueFromPhysicalId, Ut as resolveStateBucketWithDefaultAndSource, V as isTerminationProtectionPropagationError, Vt as resolveSkipPrefix, W as refStateLookupFromResource, Wt as resolveUseCdkBootstrapAssets, X as readConfigString, Xt as findLargeInlineResources, Y as configStringRefusal, Yt as MIGRATE_TMP_PREFIX, Z as replayWarn, Zt as uploadCfnTemplate, _ as createPreDeleteFinalSnapshot, _n as NestedStackChildDirectDestroyError, _t as createAssetRedirectResolver, a as DeploymentEventsStore, an as resetAwsClients, at as isRetryableTransientError, b as unsupportedFinalSnapshotError, bn as ResourceTimeoutError, bt as AssetModeResolver, c as replayFailedOperations, cn as CdkdError, ct as TemplateParser, d as IMPLICIT_DELETE_DEPENDENCIES, dn as DeployCancelledError, dt as rebuildClientForBucketRegion, en as processStackMessages, et as requireConfigString, f as computeImplicitDeleteEdges, fn as LocalInvokeBuildError$1, ft as shouldRetainResource, g as ccRoutedFinalSnapshotError, gn as MissingCdkCliError, gt as buildAssetRedirectMap, h as buildFinalSnapshotIdentifier, ht as WorkGraph, i as DeploymentEventsReader, in as getAwsClients, it as withRetry, j as red, jt as runDockerStreaming, k as gray, kn as withErrorHandling, kt as getDockerCmd, l as replayRollback, lt as LockManager, m as PRE_DELETE_SNAPSHOT_TYPES, mn as LocalStartServiceError, mt as stringifyValue, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as resolveBucketRegion, nt as DiffCalculator, o as planFailedOps, on as setAwsClients, ot as isThrottlingError, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as LocalMigrateError, pt as AssetPublisher, q as resolveExplicitPhysicalId, qt as CFN_TEMPLATE_BODY_LIMIT, r as DeployEngine, rn as AwsClients, rt as describeTypeWithThrottleRetry, s as planRollback, st as DagBuilder, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as applyRoleArnIfSet, u as withResourceDeadline, ut as S3StateBackend, v as isFinalSnapshotError, vn as PartialFailureError, vt as loadPublishableAssetManifest, w as isStatefulRecreateTargetSync, wt as parseBootstrapMarker, x as makeCanonicalizePropertiesFn, xn as ResourceUpdateNotSupportedError, xt as BOOTSTRAP_MARKER_PREFIX, y as refusesFinalSnapshot, yn as ProvisioningError, yt as rewriteTemplateAssetReferences, z as slowCcOperationTimeoutMs, zt as resolveAutoAssetStorage } from "./deploy-engine-wfKhhyCg.js";
3
3
  import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, f as withSkipPrefix, i as runStackBuffered, n as getLogger, o as PATTERN_B_NAME_OPTIONS, p as withStackName, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-BYMEE-BS.js";
4
- import { t as ASGProvider } from "./asg-provider-C3rBE_Ni.js";
4
+ import { t as ASGProvider } from "./asg-provider-CcomwF89.js";
5
5
  import { AsyncLocalStorage } from "node:async_hooks";
6
6
  import { createHash, randomBytes, randomUUID } from "node:crypto";
7
7
  import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketEncryptionCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketOwnershipControlsCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
@@ -53260,7 +53260,7 @@ function isServiceManagedTagKey(key) {
53260
53260
  * provider binds it, and that tag is REQUIRED for managed scaling. Because any
53261
53261
  * CDK ASG declares at least a `Name` tag, `Tags` is template-DECLARED, so the
53262
53262
  * #1498 carve-out (undeclared + captured-empty) correctly does not apply — and
53263
- * the tag list is an ARRAY, which {@link findRevertDroppedAwsKeys}'s walk
53263
+ * the tag list is an ARRAY, which {@link findRevertUnbaselinedAwsKeys}'s walk
53264
53264
  * compares wholesale and never descends into. Post-revert the ASG kept the
53265
53265
  * capacity provider with its managed scaling silently broken (verified live
53266
53266
  * 2026-08-10).
@@ -53332,43 +53332,48 @@ function findRevertPreservedTagKeys(drifts, desiredProperties, awsProperties) {
53332
53332
  return [...preserved].sort();
53333
53333
  }
53334
53334
  /**
53335
- * The AWS-authored property paths a `--revert` is about to DROP, for a
53336
- * resource whose state predates observed-capture (issue #1478).
53335
+ * The AWS-authored property paths a `--revert` LEAVES UNTOUCHED, for a
53336
+ * resource whose state predates observed-capture (issue #1478, semantics
53337
+ * settled by issue #1626).
53337
53338
  *
53338
- * The mechanism, and why this is not a Glue fix: `runRevert` picks the revert
53339
- * baseline as `observedProperties ?? properties`. When `observedProperties` is
53340
- * absent, the desired side is the raw TEMPLATE while the previous side is the
53341
- * AWS-CURRENT snapshot — so {@link buildRevertNewProperties}, which overwrites
53342
- * each drifted top-level key with the desired sub-shape wholesale, replaces
53343
- * every AWS-authored key inside that subtree with the template's version. For
53344
- * a Glue Iceberg table that silently wipes `table_type` /
53345
- * `metadata_location`, the same outcome as issue #1461 reached by a different
53346
- * trigger. The exposure is general: ANY resource where AWS writes into a bag
53347
- * the template does not fully declare, on state written before
53339
+ * The mechanism: `runRevert` picks the revert baseline as
53340
+ * `observedProperties ?? properties`. When `observedProperties` is absent, the
53341
+ * desired side is the raw TEMPLATE while the previous side is the AWS-CURRENT
53342
+ * snapshot — so {@link buildRevertNewProperties}, which overwrites each drifted
53343
+ * top-level key with the desired sub-shape wholesale, has no value to carry for
53344
+ * any AWS-authored key inside that subtree. For a Glue Iceberg table that
53345
+ * reaches `table_type` / `metadata_location`, the same exposure as issue #1461
53346
+ * by a different trigger; the general case is ANY resource where AWS writes
53347
+ * into a bag the template does not fully declare, on state written before
53348
53348
  * observed-capture.
53349
53349
  *
53350
- * The user DID ask to push state over AWS, so this is not silent-wrong the way
53351
- * #1461 was but it is almost certainly not what they meant, and nothing told
53352
- * them. So the chosen semantic is **warn and proceed**: it is the cheapest
53353
- * honest step, preserves today's behavior, and does not foreclose the stricter
53354
- * options (refuse outright, or treat "no observedProperties" as "previous is
53355
- * unknown" so the merge preserves).
53350
+ * #1478 shipped the **warn and proceed** semantic the values were still
53351
+ * erased, the user was merely told first while explicitly leaving the door
53352
+ * open to "treat 'no observedProperties' as 'previous is unknown' so the merge
53353
+ * preserves". Issue #1626 walked through that door for the reasons in
53354
+ * {@link mergeUntemplatedValue}: on this baseline cdkd cannot tell an
53355
+ * AWS-authored value from an out-of-band change, and resetting on a coin flip
53356
+ * is the worse error. So the paths below are now REPORTED AS PRESERVED, and
53357
+ * `mergeUntemplatedValue` is what makes that true by merging them into the bag
53358
+ * `--revert` actually SENDS — which is the only side a wholesale-replace
53359
+ * provider consults. This function is unchanged in what it computes — only in
53360
+ * what the caller does about it.
53356
53361
  *
53357
53362
  * Scoped deliberately:
53358
53363
  *
53359
53364
  * - **Only drifted top-level keys.** Non-drifted keys keep their AWS-current
53360
- * value in `buildRevertNewProperties`, so nothing under them is dropped.
53365
+ * value in `buildRevertNewProperties`, so nothing under them is at stake.
53361
53366
  * - **Only when `observedProperties` is absent.** With it present the desired
53362
53367
  * side is the deploy-time AWS snapshot, which already carries AWS-authored
53363
- * fields — dropping one there is a legitimate revert of a real console
53364
- * change, and warning about it would be noise on every run.
53368
+ * fields — removing one there is a legitimate revert of a real console
53369
+ * change, and reporting it would be noise on every run.
53365
53370
  * - **Only keys that vanish.** A key present on both sides with a different
53366
- * VALUE is the drift the user asked to revert, not a silent loss.
53371
+ * VALUE is the drift the user asked to revert, and it reverts normally.
53367
53372
  *
53368
53373
  * @returns dotted paths, sorted, e.g. `['Parameters.metadata_location']`.
53369
53374
  */
53370
- function findRevertDroppedAwsKeys(drifts, desiredProperties, awsProperties) {
53371
- const dropped = /* @__PURE__ */ new Set();
53375
+ function findRevertUnbaselinedAwsKeys(drifts, desiredProperties, awsProperties) {
53376
+ const missing = /* @__PURE__ */ new Set();
53372
53377
  const driftedTopLevelKeys = /* @__PURE__ */ new Set();
53373
53378
  for (const d of drifts) {
53374
53379
  const topLevelKey = d.path.split(".", 1)[0];
@@ -53376,9 +53381,9 @@ function findRevertDroppedAwsKeys(drifts, desiredProperties, awsProperties) {
53376
53381
  }
53377
53382
  for (const key of driftedTopLevelKeys) {
53378
53383
  if (!(key in desiredProperties)) continue;
53379
- collectMissingPaths(awsProperties[key], desiredProperties[key], key, dropped, isTagListKey(key) ? isServiceManagedTagKey : void 0);
53384
+ collectMissingPaths(awsProperties[key], desiredProperties[key], key, missing, isTagListKey(key) ? isServiceManagedTagKey : void 0);
53380
53385
  }
53381
- return [...dropped].sort();
53386
+ return [...missing].sort();
53382
53387
  }
53383
53388
  /**
53384
53389
  * Walk the AWS-side value against the desired-side value, recording every path
@@ -53412,11 +53417,12 @@ function findRevertDroppedAwsKeys(drifts, desiredProperties, awsProperties) {
53412
53417
  */
53413
53418
  function collectMissingPaths(awsValue, desiredValue, path, out, isPreservedKey) {
53414
53419
  if (isKeyedList(awsValue)) {
53415
- if (!isKeyedList(desiredValue)) {
53420
+ const desiredIsEmptyArray = Array.isArray(desiredValue) && desiredValue.length === 0;
53421
+ if (!isKeyedList(desiredValue) && !desiredIsEmptyArray) {
53416
53422
  if (desiredValue === void 0) out.add(path);
53417
53423
  return;
53418
53424
  }
53419
- const desiredKeys = new Set(desiredValue.map((e) => e.Key));
53425
+ const desiredKeys = new Set(isKeyedList(desiredValue) ? desiredValue.map((e) => e.Key) : []);
53420
53426
  for (const entry of awsValue) {
53421
53427
  if (desiredKeys.has(entry.Key)) continue;
53422
53428
  if (isPreservedKey?.(entry.Key)) continue;
@@ -53441,10 +53447,12 @@ function collectMissingPaths(awsValue, desiredValue, path, out, isPreservedKey)
53441
53447
  /**
53442
53448
  * A NON-EMPTY array whose every element is an object carrying a string `Key`.
53443
53449
  *
53444
- * Emptiness is deliberately excluded: `[]` carries no identities, so treating
53445
- * it as keyed would report nothing on the AWS side and — as the DESIRED side
53446
- * would claim every AWS entry is dropped, which is already what the
53447
- * whole-list arm reports more legibly.
53450
+ * Emptiness is excluded on the AWS side: `[]` carries no identities, so there
53451
+ * is nothing to report. It is NOT excluded on the DESIRED side any more
53452
+ * (issue #1626) — an empty baseline list means every AWS entry is unbaselined,
53453
+ * and since the only caller runs on the template-only baseline where
53454
+ * {@link mergeUntemplatedValue} PRESERVES all of them, reporting nothing would
53455
+ * leave the plan under-claiming what survives.
53448
53456
  */
53449
53457
  function isKeyedList(value) {
53450
53458
  return Array.isArray(value) && value.length > 0 && value.every((e) => typeof e === "object" && e !== null && typeof e.Key === "string");
@@ -53453,6 +53461,95 @@ function isPlainRecord(value) {
53453
53461
  return typeof value === "object" && value !== null && !Array.isArray(value);
53454
53462
  }
53455
53463
  /**
53464
+ * Merge the AWS-current value with the revert baseline, KEEPING every path the
53465
+ * baseline does not declare (issue
53466
+ * [#1626](https://github.com/go-to-k/cdkd/issues/1626) items 2 + 3).
53467
+ *
53468
+ * ## The contract this settles
53469
+ *
53470
+ * `provider.update(logicalId, physicalId, type, newProperties,
53471
+ * previousProperties)` has ONE parameter list and TWO callers:
53472
+ *
53473
+ * - the deploy engine passes the state-recorded `properties` — the
53474
+ * LAST-DEPLOYED TEMPLATE — so a path present on the previous side and absent
53475
+ * from the desired side means exactly one thing: **the user removed it from
53476
+ * the template**;
53477
+ * - `runRevert` passes the FULL `readCurrentState` snapshot, so the same
53478
+ * absence ALSO covers **a path the template never declared at all**.
53479
+ *
53480
+ * Both provider shapes then destroy that path, by different routes. One that
53481
+ * KEY-DIFFS a collection reads the absence as a REMOVAL — ELBv2's
53482
+ * `LoadBalancerAttributes`, where `readCurrentState` deliberately emits every
53483
+ * attribute AWS reports, puts ~18 untemplated attributes on the removal path
53484
+ * against a template declaring one or two. One that REPLACES a bag wholesale
53485
+ * (`PutBucketTagging` is documented full-replace; the same holds for every
53486
+ * `Put*Configuration`) never consults the previous side at all and simply
53487
+ * sends a bag the untemplated path is missing from.
53488
+ *
53489
+ * That second shape is why the fix lands on the DESIRED side rather than by
53490
+ * trimming `previousProperties`: trimming stops the key-diffing removal but
53491
+ * leaves the wholesale-replace drop untouched, so half the reported paths
53492
+ * would still be erased while the plan claimed otherwise. Merging into the bag
53493
+ * cdkd actually SENDS covers both, and is what makes the plan's "left
53494
+ * untouched" line true rather than aspirational.
53495
+ *
53496
+ * ## Scoped to the baseline that cannot tell the two apart
53497
+ *
53498
+ * Applied ONLY when `observedProperties` is absent — the same gate as
53499
+ * {@link findRevertUnbaselinedAwsKeys}'s notice, and for the same reason. With
53500
+ * observed-capture present the baseline IS a deploy-time AWS snapshot, so a
53501
+ * path AWS reports and the baseline lacks was genuinely added out-of-band and
53502
+ * removing it is the revert the user asked for (the `drift-revert` integ
53503
+ * fixture requires exactly that for an injected tag). Without it the baseline
53504
+ * is the raw TEMPLATE, and "AWS has it, the template does not" is
53505
+ * indistinguishable from "AWS authored it" — so the honest answer is to leave
53506
+ * it alone. `findRevertUnbaselinedAwsKeys`'s docstring already named this as
53507
+ * one of the stricter options #1478 did not foreclose ("treat 'no
53508
+ * observedProperties' as 'previous is unknown' so the merge preserves"); this
53509
+ * is that option.
53510
+ *
53511
+ * The structural rules mirror {@link collectMissingPaths} case for case —
53512
+ * plain records descend, KEYED `[{Key, Value}]` lists merge by `Key`,
53513
+ * positional arrays and scalars are taken from the baseline wholesale — so
53514
+ * every path the plan names as preserved IS kept, and a unit test pins that
53515
+ * correspondence by diffing the flag-on and flag-off outputs.
53516
+ *
53517
+ * A service-authored tag is absent from both sides of that comparison and
53518
+ * stays consistent: `findRevertUnbaselinedAwsKeys` omits it
53519
+ * (`isServiceManagedTagKey`) because the #1501 carve-out already preserves it
53520
+ * on the DEFAULT path, so it is not something this flag rescues and reporting
53521
+ * it would warn about a loss that cannot happen. The test pins that it
53522
+ * survives under BOTH settings.
53523
+ *
53524
+ * A path the baseline DOES declare always wins, which is what keeps the revert
53525
+ * a revert: a drifted value is reset, and one AWS dropped is re-added.
53526
+ */
53527
+ function mergeUntemplatedValue(awsValue, desiredValue) {
53528
+ const desiredIsEmptyArray = Array.isArray(desiredValue) && desiredValue.length === 0;
53529
+ if (isKeyedList(awsValue) && (isKeyedList(desiredValue) || desiredIsEmptyArray)) {
53530
+ const desiredEntries = isKeyedList(desiredValue) ? desiredValue : [];
53531
+ const desiredByKey = new Map(desiredEntries.map((e) => [e.Key, e]));
53532
+ const merged = [];
53533
+ const emitted = /* @__PURE__ */ new Set();
53534
+ for (const entry of awsValue) {
53535
+ if (emitted.has(entry.Key)) continue;
53536
+ emitted.add(entry.Key);
53537
+ merged.push(desiredByKey.get(entry.Key) ?? entry);
53538
+ }
53539
+ for (const entry of desiredEntries) {
53540
+ if (emitted.has(entry.Key)) continue;
53541
+ emitted.add(entry.Key);
53542
+ merged.push(entry);
53543
+ }
53544
+ return merged;
53545
+ }
53546
+ if (!isPlainRecord(awsValue) || !isPlainRecord(desiredValue)) return desiredValue;
53547
+ const merged = {};
53548
+ for (const [key, value] of Object.entries(awsValue)) merged[key] = key in desiredValue ? mergeUntemplatedValue(value, desiredValue[key]) : value;
53549
+ for (const [key, value] of Object.entries(desiredValue)) if (!(key in awsValue)) merged[key] = value;
53550
+ return merged;
53551
+ }
53552
+ /**
53456
53553
  * Build the `newProperties` object passed to `provider.update` during
53457
53554
  * `--revert`. Strategy:
53458
53555
  *
@@ -53494,8 +53591,16 @@ function isPlainRecord(value) {
53494
53591
  * (`Tags[0].Value`) — array drifts surface as a single entry on the
53495
53592
  * parent path — so `path.split('.', 1)` is always safe to extract the
53496
53593
  * top-level key.
53594
+ *
53595
+ * `preserveUntemplated` (issue #1626) switches the overlay from WHOLESALE to a
53596
+ * deep merge for the drifted subtrees, keeping every path the baseline does
53597
+ * not declare. `runRevert` sets it exactly when the resource has no
53598
+ * `observedProperties` — see {@link mergeUntemplatedValue} for why that is the
53599
+ * only baseline on which it is correct, and why the fix has to live on this
53600
+ * side rather than on `previousProperties`.
53497
53601
  */
53498
- function buildRevertNewProperties(drifts, desiredProperties, awsProperties) {
53602
+ function buildRevertNewProperties(drifts, desiredProperties, awsProperties, options = {}) {
53603
+ const preserveUntemplated = options.preserveUntemplated === true;
53499
53604
  const result = { ...awsProperties };
53500
53605
  for (const d of drifts) {
53501
53606
  const topLevelKey = d.path.split(".", 1)[0];
@@ -53503,8 +53608,11 @@ function buildRevertNewProperties(drifts, desiredProperties, awsProperties) {
53503
53608
  if (topLevelKey in desiredProperties) {
53504
53609
  const desiredValue = desiredProperties[topLevelKey];
53505
53610
  const awsValue = awsProperties[topLevelKey];
53506
- const baselineIsTagList = isCfnTagList(desiredValue) || Array.isArray(desiredValue) && desiredValue.length === 0;
53507
- result[topLevelKey] = isTagListKey(topLevelKey) && baselineIsTagList && isCfnTagList(awsValue) ? mergeTagListForRevert(desiredValue, awsValue) : desiredValue;
53611
+ if (preserveUntemplated) result[topLevelKey] = mergeUntemplatedValue(awsValue, desiredValue);
53612
+ else {
53613
+ const baselineIsTagList = isCfnTagList(desiredValue) || Array.isArray(desiredValue) && desiredValue.length === 0;
53614
+ result[topLevelKey] = isTagListKey(topLevelKey) && baselineIsTagList && isCfnTagList(awsValue) ? mergeTagListForRevert(desiredValue, awsValue) : desiredValue;
53615
+ }
53508
53616
  }
53509
53617
  }
53510
53618
  return result;
@@ -53567,7 +53675,7 @@ async function runRevert(reports, providerRegistry, stateConfig, awsClients, opt
53567
53675
  provisionedBy: stateResource.provisionedBy
53568
53676
  }).provider;
53569
53677
  const desiredProperties = stateResource.observedProperties ?? stateResource.properties ?? {};
53570
- const newProperties = buildRevertNewProperties(outcome.changes, desiredProperties, outcome.awsProperties);
53678
+ const newProperties = buildRevertNewProperties(outcome.changes, desiredProperties, outcome.awsProperties, { preserveUntemplated: stateResource.observedProperties === void 0 });
53571
53679
  try {
53572
53680
  await withRetry(() => provider.update(outcome.logicalId, stateResource.physicalId, outcome.resourceType, newProperties, outcome.awsProperties), outcome.logicalId, { logger: { debug: (msg) => logger.debug(msg) } });
53573
53681
  totalSucceeded++;
@@ -53634,16 +53742,17 @@ function printRevertPlan(reports) {
53634
53742
  const tagWord = preserved.length === 1 ? "tag" : "tags";
53635
53743
  process.stdout.write(` ! reverting this tag list KEEPS ${preserved.length} AWS-authored ${tagWord} the baseline does not carry:\n`);
53636
53744
  for (const path of preserved) process.stdout.write(` ${path}\n`);
53637
- process.stdout.write(" Every other tag reverts normally. A service may require these (ECS needs AmazonECSManaged for managed scaling); 'aws:'-prefixed keys are AWS-reserved and cannot be removed by hand.\n");
53745
+ const othersRevert = stateResource.observedProperties === void 0 ? "" : `Every other tag reverts normally. `;
53746
+ process.stdout.write(` ${othersRevert}A service may require these (ECS needs AmazonECSManaged for managed scaling); 'aws:'-prefixed keys are AWS-reserved and cannot be removed by hand.\n`);
53638
53747
  }
53639
53748
  }
53640
53749
  if (stateResource && stateResource.observedProperties === void 0) {
53641
- const dropped = findRevertDroppedAwsKeys(o.changes, stateResource.properties ?? {}, o.awsProperties);
53642
- if (dropped.length > 0) {
53643
- const word = dropped.length === 1 ? "value" : "values";
53644
- process.stdout.write(` ! this resource has no observed-capture baseline, so the revert pushes the raw TEMPLATE and will DROP ${dropped.length} AWS-authored ${word}:\n`);
53645
- for (const path of dropped) process.stdout.write(` ${path}\n`);
53646
- process.stdout.write(" Re-deploy the stack first to populate observedProperties if you want these preserved.\n");
53750
+ const unbaselined = findRevertUnbaselinedAwsKeys(o.changes, stateResource.properties ?? {}, o.awsProperties);
53751
+ if (unbaselined.length > 0) {
53752
+ const word = unbaselined.length === 1 ? "value" : "values";
53753
+ process.stdout.write(` ! this resource has no observed-capture baseline, so the revert pushes the raw TEMPLATE and LEAVES ${unbaselined.length} AWS-authored ${word} untouched:\n`);
53754
+ for (const path of unbaselined) process.stdout.write(` ${path}\n`);
53755
+ process.stdout.write(` The template does not declare these, so cdkd cannot tell an AWS-authored value from an out-of-band change and will not reset either (issue #1626). Run 'cdkd state refresh-observed ${report.stackName}' (or re-deploy) to populate observedProperties if you want them reverted too.\n`);
53647
53756
  }
53648
53757
  }
53649
53758
  }
@@ -70595,7 +70704,7 @@ function createMigrateCommand() {
70595
70704
  */
70596
70705
  function buildProgram() {
70597
70706
  const program = new Command();
70598
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.280.28");
70707
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.280.29");
70599
70708
  program.hook("preAction", (_thisCommand, actionCommand) => {
70600
70709
  const { profile } = actionCommand.optsWithGlobals();
70601
70710
  if (profile !== void 0) process.env["AWS_PROFILE"] = profile;