@go-to-k/cdkd 0.280.11 → 0.280.13

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 applyRoleArnIfSet, A as red, At as AssetManifestLoader, B as isTerminationProtectionPropagationError, Bt as resolveStateBucketWithDefault, C as isStatefulRecreateTargetSync, Ct as validateAssetBucketName, D as cyan, Dn as withErrorHandling, Dt as getDockerCmd, E as bold, En as normalizeAwsError, Et as formatDockerLoginError, F as ProviderRegistry, Ft as getLegacyStateBucketName, G as normalizeAwsTagsToCfn, Gt as CFN_TEMPLATE_BODY_LIMIT, H as cfnRefValueFromPhysicalId, Ht as resolveUseCdkBootstrapAssets, I as findActionableSilentDrops, It as resolveApp, J as readConfigString, Jt as findLargeInlineResources, K as resolveExplicitPhysicalId, Kt as CFN_TEMPLATE_URL_LIMIT, L as CloudControlProvider, Lt as resolveAutoAssetStorage, M as IAMRoleProvider, Mt as Synthesizer, N as collectInlinePolicyNamesManagedBySiblings, Nt as synthesisStatusMessage, O as gray, Ot as runDockerForeground, P as clearOnUpdateRemoval, Pt as getDefaultStateBucketName, Q as requireConfigString, Qt as processStackMessages, R as slowCcOperationTimeoutMs, Rt as resolveCaptureObservedState, S as MULTI_REGION_RECREATE_BLOCKED_TYPES, St as parseBootstrapMarker, T as formatResourceLine, Tt as buildDockerImage, U as refStateLookupFromResource, Ut as stateBucketExistenceConfirmed, V as IntrinsicFunctionResolver, Vt as resolveStateBucketWithDefaultAndSource, W as WAFv2WebACLProvider, Wt as warnDeprecatedNoPrefixCliFlag, X as requireConfigArray, Xt as expectedOwnerParam, Y as replayWarn, Yt as uploadCfnTemplate, Z as requireConfigObject, Zt as AssemblyReader, _ as createPreDeleteFinalSnapshot, _n as ProvisioningError, _t as rewriteTemplateAssetReferences, a as DeploymentEventsStore, at as DagBuilder, b as unsupportedFinalSnapshotError, bn as StackHasActiveImportsError, bt as ensureAssetStorage, c as replayFailedOperations, ct as S3StateBackend, d as IMPLICIT_DELETE_DEPENDENCIES, dn as LocalMigrateError, dt as AssetPublisher, en as resolveBucketRegion, et as DiffCalculator, f as computeImplicitDeleteEdges, fn as LocalStartServiceError, ft as stringifyValue, g as ccRoutedFinalSnapshotError, gn as PartialFailureError, gt as loadPublishableAssetManifest, h as buildFinalSnapshotIdentifier, hn as NestedStackChildDirectDestroyError, ht as createAssetRedirectResolver, i as DeploymentEventsReader, in as setAwsClients, it as isThrottlingError, j as yellow, jt as getDockerImageBySourceHash, k as green, kt as runDockerStreaming, l as replayRollback, ln as DeployCancelledError, lt as rebuildClientForBucketRegion, m as PRE_DELETE_SNAPSHOT_TYPES, mn as MissingCdkCliError, mt as buildAssetRedirectMap, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as getAwsClients, nt as withRetry, o as planFailedOps, on as CdkdError, ot as TemplateParser, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pt as WorkGraph, q as assertRegionMatch, qt as MIGRATE_TMP_PREFIX, r as DeployEngine, rn as resetAwsClients, rt as isRetryableTransientError, s as planRollback, st as LockManager, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as AwsClients, tt as describeTypeWithThrottleRetry, u as withResourceDeadline, un as LocalInvokeBuildError$1, ut as shouldRetainResource, v as isFinalSnapshotError, vn as ResourceTimeoutError, vt as AssetModeResolver, w as renderStatefulReason, wt as validateContainerRepoName, x as extractDeploymentEventError, xn as StackTerminationProtectionError, xt as getBootstrapMarkerKey, y as refusesFinalSnapshot, yn as ResourceUpdateNotSupportedError, yt as BOOTSTRAP_MARKER_PREFIX, z as disableInstanceApiTermination, zt as resolveSkipPrefix } from "./deploy-engine-DHGLmPWH.js";
2
+ import { $ as applyRoleArnIfSet, A as red, At as AssetManifestLoader, B as isTerminationProtectionPropagationError, Bt as resolveStateBucketWithDefault, C as isStatefulRecreateTargetSync, Ct as validateAssetBucketName, D as cyan, Dn as withErrorHandling, Dt as getDockerCmd, E as bold, En as normalizeAwsError, Et as formatDockerLoginError, F as ProviderRegistry, Ft as getLegacyStateBucketName, G as normalizeAwsTagsToCfn, Gt as CFN_TEMPLATE_BODY_LIMIT, H as cfnRefValueFromPhysicalId, Ht as resolveUseCdkBootstrapAssets, I as findActionableSilentDrops, It as resolveApp, J as readConfigString, Jt as findLargeInlineResources, K as resolveExplicitPhysicalId, Kt as CFN_TEMPLATE_URL_LIMIT, L as CloudControlProvider, Lt as resolveAutoAssetStorage, M as IAMRoleProvider, Mt as Synthesizer, N as collectInlinePolicyNamesManagedBySiblings, Nt as synthesisStatusMessage, O as gray, Ot as runDockerForeground, P as clearOnUpdateRemoval, Pt as getDefaultStateBucketName, Q as requireConfigString, Qt as processStackMessages, R as slowCcOperationTimeoutMs, Rt as resolveCaptureObservedState, S as MULTI_REGION_RECREATE_BLOCKED_TYPES, St as parseBootstrapMarker, T as formatResourceLine, Tt as buildDockerImage, U as refStateLookupFromResource, Ut as stateBucketExistenceConfirmed, V as IntrinsicFunctionResolver, Vt as resolveStateBucketWithDefaultAndSource, W as WAFv2WebACLProvider, Wt as warnDeprecatedNoPrefixCliFlag, X as requireConfigArray, Xt as expectedOwnerParam, Y as replayWarn, Yt as uploadCfnTemplate, Z as requireConfigObject, Zt as AssemblyReader, _ as createPreDeleteFinalSnapshot, _n as ProvisioningError, _t as rewriteTemplateAssetReferences, a as DeploymentEventsStore, at as DagBuilder, b as unsupportedFinalSnapshotError, bn as StackHasActiveImportsError, bt as ensureAssetStorage, c as replayFailedOperations, ct as S3StateBackend, d as IMPLICIT_DELETE_DEPENDENCIES, dn as LocalMigrateError, dt as AssetPublisher, en as resolveBucketRegion, et as DiffCalculator, f as computeImplicitDeleteEdges, fn as LocalStartServiceError, ft as stringifyValue, g as ccRoutedFinalSnapshotError, gn as PartialFailureError, gt as loadPublishableAssetManifest, h as buildFinalSnapshotIdentifier, hn as NestedStackChildDirectDestroyError, ht as createAssetRedirectResolver, i as DeploymentEventsReader, in as setAwsClients, it as isThrottlingError, j as yellow, jt as getDockerImageBySourceHash, k as green, kt as runDockerStreaming, l as replayRollback, ln as DeployCancelledError, lt as rebuildClientForBucketRegion, m as PRE_DELETE_SNAPSHOT_TYPES, mn as MissingCdkCliError, mt as buildAssetRedirectMap, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as getAwsClients, nt as withRetry, o as planFailedOps, on as CdkdError, ot as TemplateParser, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pt as WorkGraph, q as assertRegionMatch, qt as MIGRATE_TMP_PREFIX, r as DeployEngine, rn as resetAwsClients, rt as isRetryableTransientError, s as planRollback, st as LockManager, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as AwsClients, tt as describeTypeWithThrottleRetry, u as withResourceDeadline, un as LocalInvokeBuildError$1, ut as shouldRetainResource, v as isFinalSnapshotError, vn as ResourceTimeoutError, vt as AssetModeResolver, w as renderStatefulReason, wt as validateContainerRepoName, x as extractDeploymentEventError, xn as StackTerminationProtectionError, xt as getBootstrapMarkerKey, y as refusesFinalSnapshot, yn as ResourceUpdateNotSupportedError, yt as BOOTSTRAP_MARKER_PREFIX, z as disableInstanceApiTermination, zt as resolveSkipPrefix } from "./deploy-engine-CE-J9Vlu.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-CN0zTvF5.js";
4
+ import { t as ASGProvider } from "./asg-provider-DJSCLI9C.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";
@@ -31,7 +31,7 @@ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
31
31
  import { execFile, spawn } from "node:child_process";
32
32
  import { tmpdir } from "node:os";
33
33
  import { AssociateVPCWithHostedZoneCommand, ChangeResourceRecordSetsCommand, ChangeTagsForResourceCommand, CreateHostedZoneCommand, CreateQueryLoggingConfigCommand, DeleteHostedZoneCommand, DeleteQueryLoggingConfigCommand, DisassociateVPCFromHostedZoneCommand, GetHostedZoneCommand, ListHostedZonesByNameCommand, ListQueryLoggingConfigsCommand, ListResourceRecordSetsCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$8, Route53Client, UpdateHostedZoneCommentCommand, UpdateHostedZoneFeaturesCommand } from "@aws-sdk/client-route-53";
34
- import { AddTagsCommand, CreateListenerCommand, CreateLoadBalancerCommand, CreateTargetGroupCommand, DeleteListenerCommand, DeleteLoadBalancerCommand, DeleteTargetGroupCommand, DescribeListenerAttributesCommand, DescribeListenersCommand, DescribeLoadBalancerAttributesCommand, DescribeLoadBalancersCommand, DescribeTagsCommand, DescribeTargetGroupsCommand, ElasticLoadBalancingV2Client, ModifyListenerAttributesCommand, ModifyListenerCommand, ModifyLoadBalancerAttributesCommand, ModifyTargetGroupCommand, RemoveTagsCommand, SetIpAddressTypeCommand, SetSecurityGroupsCommand, SetSubnetsCommand, waitUntilLoadBalancerAvailable } from "@aws-sdk/client-elastic-load-balancing-v2";
34
+ import { AddTagsCommand, CreateListenerCommand, CreateLoadBalancerCommand, CreateTargetGroupCommand, DeleteListenerCommand, DeleteLoadBalancerCommand, DeleteTargetGroupCommand, DeregisterTargetsCommand, DescribeCapacityReservationCommand, DescribeListenerAttributesCommand, DescribeListenersCommand, DescribeLoadBalancerAttributesCommand, DescribeLoadBalancersCommand, DescribeTagsCommand, DescribeTargetGroupAttributesCommand, DescribeTargetGroupsCommand, ElasticLoadBalancingV2Client, ModifyCapacityReservationCommand, ModifyIpPoolsCommand, ModifyListenerAttributesCommand, ModifyListenerCommand, ModifyLoadBalancerAttributesCommand, ModifyTargetGroupAttributesCommand, ModifyTargetGroupCommand, RegisterTargetsCommand, RemoveTagsCommand, SetIpAddressTypeCommand, SetSecurityGroupsCommand, SetSubnetsCommand, waitUntilLoadBalancerAvailable } from "@aws-sdk/client-elastic-load-balancing-v2";
35
35
  import { CreateAliasCommand, CreateKeyCommand, DeleteAliasCommand, DescribeKeyCommand, DisableKeyCommand, DisableKeyRotationCommand, EnableKeyCommand, EnableKeyRotationCommand, GetKeyPolicyCommand, GetKeyRotationStatusCommand, KMSClient, ListAliasesCommand, ListResourceTagsCommand, NotFoundException as NotFoundException$2, PutKeyPolicyCommand, ScheduleKeyDeletionCommand, TagResourceCommand as TagResourceCommand$11, UntagResourceCommand as UntagResourceCommand$11, UpdateAliasCommand, UpdateKeyDescriptionCommand } from "@aws-sdk/client-kms";
36
36
  import { join as join$1 } from "path";
37
37
  import { BatchDeleteImageCommand, CreateRepositoryCommand, DeleteLifecyclePolicyCommand, DeleteRepositoryCommand, DeleteRepositoryPolicyCommand, DescribeImagesCommand as DescribeImagesCommand$1, DescribeRepositoriesCommand, ECRClient, GetAuthorizationTokenCommand, GetLifecyclePolicyCommand, LifecyclePolicyNotFoundException, ListTagsForResourceCommand as ListTagsForResourceCommand$9, PutImageScanningConfigurationCommand, PutImageTagMutabilityCommand, PutLifecyclePolicyCommand, RepositoryNotEmptyException, RepositoryNotFoundException, SetRepositoryPolicyCommand, TagResourceCommand as TagResourceCommand$12, UntagResourceCommand as UntagResourceCommand$12 } from "@aws-sdk/client-ecr";
@@ -27508,6 +27508,36 @@ var ECSProvider = class {
27508
27508
  //#endregion
27509
27509
  //#region src/provisioning/providers/elbv2-provider.ts
27510
27510
  /**
27511
+ * Test seam for the capacity-reservation stabilize poll (mirrors
27512
+ * `finalSnapshotDelays` in ../final-snapshot.ts). Unit tests stub `sleep`
27513
+ * so the poll runs without wall-clock delay.
27514
+ */
27515
+ const capacityReservationDelays = { sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)) };
27516
+ /**
27517
+ * Documented AWS defaults for TargetGroup attributes, used to reset a
27518
+ * removed `TargetGroupAttributes` entry. Unlike ModifyLoadBalancerAttributes
27519
+ * / ModifyListenerAttributes, ModifyTargetGroupAttributes REJECTS an empty
27520
+ * `Value` ("A target group attribute value must be specified" — live-verified
27521
+ * 2026-08-11), so clearing an override requires sending the documented
27522
+ * default explicitly. Keys whose default is target-type-dependent or
27523
+ * undocumented are deliberately absent — a removal of those warns and
27524
+ * retains the live value instead of guessing.
27525
+ * Source: ELBv2 API reference, TargetGroupAttribute key table.
27526
+ */
27527
+ const TARGET_GROUP_ATTRIBUTE_DEFAULTS = {
27528
+ "deregistration_delay.timeout_seconds": "300",
27529
+ "deregistration_delay.connection_termination.enabled": "false",
27530
+ "stickiness.enabled": "false",
27531
+ "stickiness.lb_cookie.duration_seconds": "86400",
27532
+ "stickiness.app_cookie.duration_seconds": "86400",
27533
+ "load_balancing.algorithm.type": "round_robin",
27534
+ "load_balancing.algorithm.anomaly_mitigation": "off",
27535
+ "load_balancing.cross_zone.enabled": "use_load_balancer_configuration",
27536
+ "slow_start.duration_seconds": "0",
27537
+ "lambda.multi_value_headers.enabled": "false",
27538
+ "proxy_protocol_v2.enabled": "false"
27539
+ };
27540
+ /**
27511
27541
  * AWS ELBv2 Provider
27512
27542
  *
27513
27543
  * Implements resource provisioning for ELBv2 resources:
@@ -27540,7 +27570,12 @@ var ELBv2Provider = class {
27540
27570
  "Type",
27541
27571
  "IpAddressType",
27542
27572
  "LoadBalancerAttributes",
27543
- "Tags"
27573
+ "Tags",
27574
+ "EnablePrefixForIpv6SourceNat",
27575
+ "EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic",
27576
+ "Ipv4IpamPoolId",
27577
+ "MinimumLoadBalancerCapacity",
27578
+ "EnableCapacityReservationProvisionStabilize"
27544
27579
  ])],
27545
27580
  ["AWS::ElasticLoadBalancingV2::TargetGroup", /* @__PURE__ */ new Set([
27546
27581
  "Protocol",
@@ -27558,7 +27593,11 @@ var ELBv2Provider = class {
27558
27593
  "UnhealthyThresholdCount",
27559
27594
  "Matcher",
27560
27595
  "Name",
27561
- "Tags"
27596
+ "Tags",
27597
+ "IpAddressType",
27598
+ "TargetControlPort",
27599
+ "TargetGroupAttributes",
27600
+ "Targets"
27562
27601
  ])],
27563
27602
  ["AWS::ElasticLoadBalancingV2::Listener", /* @__PURE__ */ new Set([
27564
27603
  "LoadBalancerArn",
@@ -27614,6 +27653,7 @@ var ELBv2Provider = class {
27614
27653
  try {
27615
27654
  const tags = this.extractTags(properties);
27616
27655
  const lbName = generateResourceNameWithFallback(properties["Name"], logicalId, { maxLength: 32 });
27656
+ const ipv4IpamPoolId = properties["Ipv4IpamPoolId"];
27617
27657
  const lb = (await this.getClient().send(new CreateLoadBalancerCommand({
27618
27658
  Name: lbName,
27619
27659
  Subnets: properties["Subnets"],
@@ -27622,6 +27662,8 @@ var ELBv2Provider = class {
27622
27662
  Scheme: properties["Scheme"],
27623
27663
  Type: properties["Type"],
27624
27664
  IpAddressType: properties["IpAddressType"],
27665
+ EnablePrefixForIpv6SourceNat: properties["EnablePrefixForIpv6SourceNat"],
27666
+ ...ipv4IpamPoolId !== void 0 && { IpamPools: { Ipv4IpamPoolId: ipv4IpamPoolId } },
27625
27667
  ...tags.length > 0 && { Tags: tags }
27626
27668
  }))).LoadBalancers?.[0];
27627
27669
  if (!lb || !lb.LoadBalancerArn) throw new Error("CreateLoadBalancer did not return LoadBalancer ARN");
@@ -27638,17 +27680,32 @@ var ELBv2Provider = class {
27638
27680
  }, { LoadBalancerArns: [lbArn] });
27639
27681
  this.logger.debug(`LoadBalancer ${logicalId} is active`);
27640
27682
  } else this.logger.debug(`LoadBalancer ${logicalId} created (skipping active-state wait per --no-wait)`);
27641
- const lbAttributes = properties["LoadBalancerAttributes"];
27642
- if (lbAttributes && lbAttributes.length > 0) {
27683
+ const lbAttributes = this.normalizeAttributes(properties["LoadBalancerAttributes"]);
27684
+ if (lbAttributes.length > 0) {
27643
27685
  await this.getClient().send(new ModifyLoadBalancerAttributesCommand({
27644
27686
  LoadBalancerArn: lbArn,
27645
- Attributes: lbAttributes.map((attr) => ({
27646
- Key: attr.Key,
27647
- Value: attr.Value
27648
- }))
27687
+ Attributes: lbAttributes
27649
27688
  }));
27650
27689
  this.logger.debug(`Applied ${lbAttributes.length} LoadBalancer attributes for ${logicalId}`);
27651
27690
  }
27691
+ const enforcePrivateLink = properties["EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic"];
27692
+ if (enforcePrivateLink !== void 0) {
27693
+ await this.getClient().send(new SetSecurityGroupsCommand({
27694
+ LoadBalancerArn: lbArn,
27695
+ SecurityGroups: properties["SecurityGroups"] ?? [],
27696
+ EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic: enforcePrivateLink
27697
+ }));
27698
+ this.logger.debug(`Applied EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic=${enforcePrivateLink} for ${logicalId}`);
27699
+ }
27700
+ const minCapacity = properties["MinimumLoadBalancerCapacity"];
27701
+ if (minCapacity?.CapacityUnits !== void 0) {
27702
+ await this.getClient().send(new ModifyCapacityReservationCommand({
27703
+ LoadBalancerArn: lbArn,
27704
+ MinimumLoadBalancerCapacity: { CapacityUnits: Number(minCapacity.CapacityUnits) }
27705
+ }));
27706
+ this.logger.debug(`Requested capacity reservation of ${minCapacity.CapacityUnits} LCU for ${logicalId}`);
27707
+ if (isTruthyCfnBoolean(properties["EnableCapacityReservationProvisionStabilize"]) && process.env["CDKD_NO_WAIT"] !== "true") await this.waitForCapacityReservationProvisioned(lbArn, logicalId);
27708
+ }
27652
27709
  } catch (innerError) {
27653
27710
  try {
27654
27711
  await this.getClient().send(new DeleteLoadBalancerCommand({ LoadBalancerArn: lbArn }));
@@ -27680,7 +27737,12 @@ var ELBv2Provider = class {
27680
27737
  "SubnetMappings",
27681
27738
  "SecurityGroups",
27682
27739
  "IpAddressType",
27683
- "Tags"
27740
+ "Tags",
27741
+ "EnablePrefixForIpv6SourceNat",
27742
+ "EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic",
27743
+ "Ipv4IpamPoolId",
27744
+ "MinimumLoadBalancerCapacity",
27745
+ "EnableCapacityReservationProvisionStabilize"
27684
27746
  ]);
27685
27747
  const stripHandled = (p) => {
27686
27748
  const out = {};
@@ -27688,19 +27750,7 @@ var ELBv2Provider = class {
27688
27750
  return out;
27689
27751
  };
27690
27752
  if (JSON.stringify(stripHandled(properties)) !== JSON.stringify(stripHandled(previousProperties))) throw new ResourceUpdateNotSupportedError("AWS::ElasticLoadBalancingV2::LoadBalancer", logicalId, "ELBv2 LoadBalancer Name / Type / Scheme are immutable on AWS — none of the ELBv2 Modify* / Set* APIs accept these fields; they are fixed at creation. cdkd handles LoadBalancerAttributes / Subnets / SubnetMappings / SecurityGroups / IpAddressType / Tags in-place; for Name / Type / Scheme re-deploy with cdkd deploy --replace, or destroy + redeploy the stack.");
27691
- const newAttrs = properties["LoadBalancerAttributes"] ?? [];
27692
- const oldAttrs = previousProperties["LoadBalancerAttributes"] ?? [];
27693
- const newAttrMap = new Map(newAttrs.map((a) => [a.Key, a.Value]));
27694
- const oldAttrMap = new Map(oldAttrs.map((a) => [a.Key, a.Value]));
27695
- const submittedAttrs = [];
27696
- for (const [k, v] of newAttrMap) if (oldAttrMap.get(k) !== v) submittedAttrs.push({
27697
- Key: k,
27698
- Value: v
27699
- });
27700
- for (const [k] of oldAttrMap) if (!newAttrMap.has(k)) submittedAttrs.push({
27701
- Key: k,
27702
- Value: ""
27703
- });
27753
+ const submittedAttrs = this.diffAttributes(this.normalizeAttributes(properties["LoadBalancerAttributes"]), this.normalizeAttributes(previousProperties["LoadBalancerAttributes"]));
27704
27754
  if (submittedAttrs.length > 0) {
27705
27755
  await this.getClient().send(new ModifyLoadBalancerAttributesCommand({
27706
27756
  LoadBalancerArn: physicalId,
@@ -27712,21 +27762,28 @@ var ELBv2Provider = class {
27712
27762
  const oldSubnets = previousProperties["Subnets"];
27713
27763
  const newMappings = properties["SubnetMappings"];
27714
27764
  const oldMappings = previousProperties["SubnetMappings"];
27765
+ const newIpv6SourceNat = properties["EnablePrefixForIpv6SourceNat"];
27766
+ const oldIpv6SourceNat = previousProperties["EnablePrefixForIpv6SourceNat"];
27715
27767
  const subnetsChanged = JSON.stringify(newSubnets) !== JSON.stringify(oldSubnets);
27716
27768
  const mappingsChanged = JSON.stringify(newMappings) !== JSON.stringify(oldMappings);
27717
- if (subnetsChanged || mappingsChanged) {
27769
+ if (subnetsChanged || mappingsChanged || newIpv6SourceNat !== void 0 && newIpv6SourceNat !== oldIpv6SourceNat) {
27718
27770
  await this.getClient().send(new SetSubnetsCommand({
27719
27771
  LoadBalancerArn: physicalId,
27720
- ...newMappings && newMappings.length > 0 ? { SubnetMappings: newMappings } : { Subnets: newSubnets }
27772
+ ...newMappings && newMappings.length > 0 ? { SubnetMappings: newMappings } : { Subnets: newSubnets },
27773
+ ...newIpv6SourceNat !== void 0 && { EnablePrefixForIpv6SourceNat: newIpv6SourceNat }
27721
27774
  }));
27722
27775
  this.logger.debug(`Updated Subnets / SubnetMappings for ${logicalId}`);
27723
27776
  }
27724
27777
  const newSGs = properties["SecurityGroups"];
27725
27778
  const oldSGs = previousProperties["SecurityGroups"];
27726
- if (JSON.stringify(newSGs) !== JSON.stringify(oldSGs)) {
27779
+ const newEnforce = properties["EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic"];
27780
+ const oldEnforce = previousProperties["EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic"];
27781
+ const enforceChanged = newEnforce !== void 0 && newEnforce !== oldEnforce;
27782
+ if (JSON.stringify(newSGs) !== JSON.stringify(oldSGs) || enforceChanged) {
27727
27783
  await this.getClient().send(new SetSecurityGroupsCommand({
27728
27784
  LoadBalancerArn: physicalId,
27729
- SecurityGroups: newSGs ?? []
27785
+ SecurityGroups: newSGs ?? [],
27786
+ ...newEnforce !== void 0 && { EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic: newEnforce }
27730
27787
  }));
27731
27788
  this.logger.debug(`Updated SecurityGroups for ${logicalId}`);
27732
27789
  }
@@ -27739,12 +27796,64 @@ var ELBv2Provider = class {
27739
27796
  }));
27740
27797
  this.logger.debug(`Updated IpAddressType for ${logicalId}`);
27741
27798
  }
27799
+ const newIpamPool = properties["Ipv4IpamPoolId"];
27800
+ if (newIpamPool !== previousProperties["Ipv4IpamPoolId"]) {
27801
+ await this.getClient().send(new ModifyIpPoolsCommand({
27802
+ LoadBalancerArn: physicalId,
27803
+ ...newIpamPool !== void 0 ? { IpamPools: { Ipv4IpamPoolId: newIpamPool } } : { RemoveIpamPools: ["ipv4"] }
27804
+ }));
27805
+ this.logger.debug(newIpamPool !== void 0 ? `Updated Ipv4IpamPoolId for ${logicalId}` : `Removed IPAM pool association for ${logicalId}`);
27806
+ }
27807
+ const newCapacity = properties["MinimumLoadBalancerCapacity"];
27808
+ const oldCapacity = previousProperties["MinimumLoadBalancerCapacity"];
27809
+ const newCapacityUnits = newCapacity?.CapacityUnits !== void 0 ? Number(newCapacity.CapacityUnits) : void 0;
27810
+ if (newCapacityUnits !== (oldCapacity?.CapacityUnits !== void 0 ? Number(oldCapacity.CapacityUnits) : void 0)) {
27811
+ await this.getClient().send(new ModifyCapacityReservationCommand({
27812
+ LoadBalancerArn: physicalId,
27813
+ ...newCapacityUnits !== void 0 ? { MinimumLoadBalancerCapacity: { CapacityUnits: newCapacityUnits } } : { ResetCapacityReservation: true }
27814
+ }));
27815
+ this.logger.debug(newCapacityUnits !== void 0 ? `Requested capacity reservation of ${newCapacityUnits} LCU for ${logicalId}` : `Reset capacity reservation for ${logicalId}`);
27816
+ if (newCapacityUnits !== void 0 && isTruthyCfnBoolean(properties["EnableCapacityReservationProvisionStabilize"]) && process.env["CDKD_NO_WAIT"] !== "true") await this.waitForCapacityReservationProvisioned(physicalId, logicalId);
27817
+ }
27742
27818
  await this.applyTagDiff(physicalId, previousProperties["Tags"], properties["Tags"]);
27743
27819
  return {
27744
27820
  physicalId,
27745
27821
  wasReplaced: false
27746
27822
  };
27747
27823
  }
27824
+ /**
27825
+ * Poll DescribeCapacityReservation until every zonal state reports
27826
+ * `provisioned` — the semantics of the CFn-only
27827
+ * `EnableCapacityReservationProvisionStabilize` flag (no SDK member; it
27828
+ * opts the deploy into waiting for the reservation instead of returning
27829
+ * while zones are still `pending`). Bounded at ~10 min / 15s interval;
27830
+ * a timeout WARNS and continues (the reservation keeps provisioning
27831
+ * asynchronously and the ModifyCapacityReservation was accepted), while a
27832
+ * `failed` zonal state throws — that reservation will never provision.
27833
+ */
27834
+ async waitForCapacityReservationProvisioned(lbArn, logicalId) {
27835
+ const maxAttempts = 40;
27836
+ const intervalMs = 15e3;
27837
+ this.logger.debug(`Waiting for capacity reservation of ${logicalId} to provision...`);
27838
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
27839
+ let zones;
27840
+ try {
27841
+ zones = (await this.getClient().send(new DescribeCapacityReservationCommand({ LoadBalancerArn: lbArn }))).CapacityReservationState ?? [];
27842
+ } catch (probeError) {
27843
+ this.logger.debug(`DescribeCapacityReservation for ${logicalId} failed transiently (attempt ${attempt + 1}/${maxAttempts}): ${probeError instanceof Error ? probeError.message : String(probeError)}`);
27844
+ await capacityReservationDelays.sleep(intervalMs);
27845
+ continue;
27846
+ }
27847
+ const failed = zones.find((z) => z.State?.Code === "failed");
27848
+ if (failed) throw new Error(`Capacity reservation for ${logicalId} failed in ${failed.AvailabilityZone ?? "an availability zone"}: ${failed.State?.Reason ?? "no reason reported"}`);
27849
+ if (zones.length === 0 || zones.every((z) => z.State?.Code === "provisioned")) {
27850
+ this.logger.debug(`Capacity reservation for ${logicalId} is provisioned`);
27851
+ return;
27852
+ }
27853
+ await capacityReservationDelays.sleep(intervalMs);
27854
+ }
27855
+ this.logger.warn(`Capacity reservation for ${logicalId} did not reach 'provisioned' within ${maxAttempts * intervalMs / 6e4} minutes; continuing (provisioning completes asynchronously)`);
27856
+ }
27748
27857
  async deleteLoadBalancer(logicalId, physicalId, resourceType, context) {
27749
27858
  this.logger.debug(`Deleting LoadBalancer ${logicalId}: ${physicalId}`);
27750
27859
  if (context?.removeProtection === true) try {
@@ -27793,16 +27902,45 @@ var ELBv2Provider = class {
27793
27902
  HealthCheckTimeoutSeconds: properties["HealthCheckTimeoutSeconds"] !== void 0 ? Number(properties["HealthCheckTimeoutSeconds"]) : void 0,
27794
27903
  HealthyThresholdCount: properties["HealthyThresholdCount"] !== void 0 ? Number(properties["HealthyThresholdCount"]) : void 0,
27795
27904
  UnhealthyThresholdCount: properties["UnhealthyThresholdCount"] !== void 0 ? Number(properties["UnhealthyThresholdCount"]) : void 0,
27905
+ IpAddressType: properties["IpAddressType"],
27906
+ TargetControlPort: properties["TargetControlPort"] !== void 0 ? Number(properties["TargetControlPort"]) : void 0,
27796
27907
  ...matcher && { Matcher: matcher },
27797
27908
  ...tags.length > 0 && { Tags: tags }
27798
27909
  }))).TargetGroups?.[0];
27799
27910
  if (!tg || !tg.TargetGroupArn) throw new Error("CreateTargetGroup did not return TargetGroup ARN");
27800
- this.logger.debug(`Successfully created TargetGroup ${logicalId}: ${tg.TargetGroupArn}`);
27911
+ const tgArn = tg.TargetGroupArn;
27912
+ this.logger.debug(`Successfully created TargetGroup ${logicalId}: ${tgArn}`);
27913
+ try {
27914
+ const tgAttributes = this.normalizeAttributes(properties["TargetGroupAttributes"]);
27915
+ if (tgAttributes.length > 0) {
27916
+ await this.getClient().send(new ModifyTargetGroupAttributesCommand({
27917
+ TargetGroupArn: tgArn,
27918
+ Attributes: tgAttributes
27919
+ }));
27920
+ this.logger.debug(`Applied ${tgAttributes.length} TargetGroup attribute(s) for ${logicalId}`);
27921
+ }
27922
+ const targets = this.convertTargets(properties["Targets"]);
27923
+ if (targets.length > 0) {
27924
+ await this.getClient().send(new RegisterTargetsCommand({
27925
+ TargetGroupArn: tgArn,
27926
+ Targets: targets
27927
+ }));
27928
+ this.logger.debug(`Registered ${targets.length} target(s) for ${logicalId}`);
27929
+ }
27930
+ } catch (innerError) {
27931
+ try {
27932
+ await this.getClient().send(new DeleteTargetGroupCommand({ TargetGroupArn: tgArn }));
27933
+ this.logger.debug(`Cleaned up partially-created TargetGroup ${logicalId} (${tgArn}) after wiring failure`);
27934
+ } catch (cleanupError) {
27935
+ this.logger.warn(`Failed to clean up partially-created TargetGroup ${logicalId} (${tgArn}): ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}. Manual deletion may be required before the next deploy: aws elbv2 delete-target-group --target-group-arn ${tgArn}`);
27936
+ }
27937
+ throw innerError;
27938
+ }
27801
27939
  return {
27802
- physicalId: tg.TargetGroupArn,
27940
+ physicalId: tgArn,
27803
27941
  attributes: {
27804
- TargetGroupArn: tg.TargetGroupArn,
27805
- TargetGroupFullName: tg.TargetGroupArn?.split(":").pop()?.replace("targetgroup/", ""),
27942
+ TargetGroupArn: tgArn,
27943
+ TargetGroupFullName: tgArn.split(":").pop()?.replace("targetgroup/", ""),
27806
27944
  TargetGroupName: tg.TargetGroupName
27807
27945
  }
27808
27946
  };
@@ -27813,6 +27951,13 @@ var ELBv2Provider = class {
27813
27951
  }
27814
27952
  async updateTargetGroup(logicalId, physicalId, resourceType, properties, previousProperties) {
27815
27953
  this.logger.debug(`Updating TargetGroup ${logicalId}: ${physicalId}`);
27954
+ const immutableChanged = (key, coerce) => {
27955
+ const next = properties[key];
27956
+ const prev = previousProperties[key];
27957
+ if (coerce) return (next !== void 0 ? Number(next) : void 0) !== (prev !== void 0 ? Number(prev) : void 0);
27958
+ return JSON.stringify(next) !== JSON.stringify(prev);
27959
+ };
27960
+ for (const [immutableKey, coerce] of [["IpAddressType", false], ["TargetControlPort", true]]) if (immutableChanged(immutableKey, coerce)) throw new ResourceUpdateNotSupportedError(resourceType, logicalId, `ELBv2 TargetGroup ${immutableKey} is immutable on AWS — no Modify* API accepts it; it is fixed at creation. Re-deploy with cdkd deploy --replace, or destroy + redeploy the stack.`);
27816
27961
  try {
27817
27962
  const rawMatcher = properties["Matcher"];
27818
27963
  const matcher = rawMatcher && (rawMatcher.HttpCode !== void 0 || rawMatcher.GrpcCode !== void 0) ? rawMatcher : void 0;
@@ -27829,6 +27974,43 @@ var ELBv2Provider = class {
27829
27974
  UnhealthyThresholdCount: properties["UnhealthyThresholdCount"] !== void 0 ? Number(properties["UnhealthyThresholdCount"]) : void 0,
27830
27975
  ...matcher && { Matcher: matcher }
27831
27976
  }));
27977
+ const tgAttrDiff = this.diffAttributes(this.normalizeAttributes(properties["TargetGroupAttributes"]), this.normalizeAttributes(previousProperties["TargetGroupAttributes"]), (key) => {
27978
+ const fallback = TARGET_GROUP_ATTRIBUTE_DEFAULTS[key];
27979
+ if (fallback === void 0) this.logger.warn(`TargetGroup attribute ${key} was removed from the template but has no documented default cdkd can reset it to — the live value is retained. Set the attribute explicitly to change it.`);
27980
+ return fallback;
27981
+ });
27982
+ if (tgAttrDiff.length > 0) {
27983
+ await this.getClient().send(new ModifyTargetGroupAttributesCommand({
27984
+ TargetGroupArn: physicalId,
27985
+ Attributes: tgAttrDiff
27986
+ }));
27987
+ this.logger.debug(`Applied ${tgAttrDiff.length} TargetGroupAttributes change(s) for ${logicalId}`);
27988
+ }
27989
+ const newTargets = this.convertTargets(properties["Targets"]);
27990
+ const oldTargets = this.convertTargets(previousProperties["Targets"]);
27991
+ const targetKey = (t) => JSON.stringify([
27992
+ t.Id,
27993
+ t.Port ?? null,
27994
+ t.AvailabilityZone ?? null
27995
+ ]);
27996
+ const oldTargetKeys = new Set(oldTargets.map(targetKey));
27997
+ const newTargetKeys = new Set(newTargets.map(targetKey));
27998
+ const targetsToRegister = newTargets.filter((t) => !oldTargetKeys.has(targetKey(t)));
27999
+ const targetsToDeregister = oldTargets.filter((t) => !newTargetKeys.has(targetKey(t)));
28000
+ if (targetsToRegister.length > 0) {
28001
+ await this.getClient().send(new RegisterTargetsCommand({
28002
+ TargetGroupArn: physicalId,
28003
+ Targets: targetsToRegister
28004
+ }));
28005
+ this.logger.debug(`Registered ${targetsToRegister.length} target(s) for ${logicalId}`);
28006
+ }
28007
+ if (targetsToDeregister.length > 0) {
28008
+ await this.getClient().send(new DeregisterTargetsCommand({
28009
+ TargetGroupArn: physicalId,
28010
+ Targets: targetsToDeregister
28011
+ }));
28012
+ this.logger.debug(`Deregistered ${targetsToDeregister.length} target(s) for ${logicalId}`);
28013
+ }
27832
28014
  const tg = (await this.getClient().send(new DescribeTargetGroupsCommand({ TargetGroupArns: [physicalId] }))).TargetGroups?.[0];
27833
28015
  await this.applyTagDiff(physicalId, previousProperties["Tags"], properties["Tags"]);
27834
28016
  this.logger.debug(`Successfully updated TargetGroup ${logicalId}`);
@@ -27927,19 +28109,7 @@ var ELBv2Provider = class {
27927
28109
  ...alpnPolicy && alpnPolicy.length > 0 && { AlpnPolicy: alpnPolicy },
27928
28110
  ...mutualAuth !== void 0 && { MutualAuthentication: mutualAuth }
27929
28111
  }));
27930
- const newAttrs = this.normalizeAttributes(properties["ListenerAttributes"]);
27931
- const oldAttrs = this.normalizeAttributes(previousProperties["ListenerAttributes"]);
27932
- const newAttrMap = new Map(newAttrs.map((a) => [a.Key, a.Value]));
27933
- const oldAttrMap = new Map(oldAttrs.map((a) => [a.Key, a.Value]));
27934
- const submittedAttrs = [];
27935
- for (const [k, v] of newAttrMap) if (oldAttrMap.get(k) !== v) submittedAttrs.push({
27936
- Key: k,
27937
- Value: v
27938
- });
27939
- for (const [k] of oldAttrMap) if (!newAttrMap.has(k)) submittedAttrs.push({
27940
- Key: k,
27941
- Value: ""
27942
- });
28112
+ const submittedAttrs = this.diffAttributes(this.normalizeAttributes(properties["ListenerAttributes"]), this.normalizeAttributes(previousProperties["ListenerAttributes"]));
27943
28113
  if (submittedAttrs.length > 0) {
27944
28114
  await withRetry(() => this.getClient().send(new ModifyListenerAttributesCommand({
27945
28115
  ListenerArn: physicalId,
@@ -28015,6 +28185,58 @@ var ELBv2Provider = class {
28015
28185
  return out;
28016
28186
  }
28017
28187
  /**
28188
+ * Key-diff two normalized `{Key, Value}` attribute lists into the payload
28189
+ * for a Modify*Attributes call: changed values from `newAttrs` win, and
28190
+ * keys present only in `oldAttrs` are pushed back through `removalValue` —
28191
+ * by default the empty string, which ModifyLoadBalancerAttributes /
28192
+ * ModifyListenerAttributes accept as "clear the override". A resolver
28193
+ * returning `undefined` SKIPS the entry (the resolver owns any warn).
28194
+ * An empty return means nothing changed and the call should be skipped.
28195
+ * Shared by the LoadBalancer / TargetGroup / Listener update paths.
28196
+ */
28197
+ diffAttributes(newAttrs, oldAttrs, removalValue = () => "") {
28198
+ const newAttrMap = new Map(newAttrs.map((a) => [a.Key, a.Value]));
28199
+ const oldAttrMap = new Map(oldAttrs.map((a) => [a.Key, a.Value]));
28200
+ const submitted = [];
28201
+ for (const [k, v] of newAttrMap) if (oldAttrMap.get(k) !== v) submitted.push({
28202
+ Key: k,
28203
+ Value: v
28204
+ });
28205
+ for (const [k] of oldAttrMap) if (!newAttrMap.has(k)) {
28206
+ const value = removalValue(k);
28207
+ if (value !== void 0) submitted.push({
28208
+ Key: k,
28209
+ Value: value
28210
+ });
28211
+ }
28212
+ return submitted;
28213
+ }
28214
+ /**
28215
+ * Normalize a CFn `Targets` value (an array of `{ Id, Port?,
28216
+ * AvailabilityZone? }` objects) into the SDK's `TargetDescription[]` shape.
28217
+ * Entries missing an `Id` are dropped (Id is the only required member);
28218
+ * `Port` is numeric-coerced (CFn templates may carry it as a string).
28219
+ * Returns `[]` for an absent / non-array value so callers can branch on
28220
+ * `.length`.
28221
+ */
28222
+ convertTargets(raw) {
28223
+ if (!Array.isArray(raw)) return [];
28224
+ const out = [];
28225
+ for (const entry of raw) {
28226
+ if (entry === null || typeof entry !== "object" || typeof entry.Id !== "string" || entry.Id.length === 0) {
28227
+ this.logger.warn(`Dropping malformed TargetGroup Targets entry (missing string Id): ${JSON.stringify(entry)}`);
28228
+ continue;
28229
+ }
28230
+ const e = entry;
28231
+ out.push({
28232
+ Id: e.Id,
28233
+ ...e.Port !== void 0 && { Port: Number(e.Port) },
28234
+ ...e.AvailabilityZone !== void 0 && { AvailabilityZone: e.AvailabilityZone }
28235
+ });
28236
+ }
28237
+ return out;
28238
+ }
28239
+ /**
28018
28240
  * Apply a diff between old and new CFn-shape Tags arrays via ELBv2's
28019
28241
  * `AddTags` / `RemoveTags` APIs. Both accept `ResourceArns: [arn]`
28020
28242
  * (single ARN), `Tags: [{Key, Value}]` for AddTags, and
@@ -28112,6 +28334,15 @@ var ELBv2Provider = class {
28112
28334
  if (lb.Scheme !== void 0) result["Scheme"] = lb.Scheme;
28113
28335
  if (lb.Type !== void 0) result["Type"] = lb.Type;
28114
28336
  if (lb.IpAddressType !== void 0) result["IpAddressType"] = lb.IpAddressType;
28337
+ if (lb.EnablePrefixForIpv6SourceNat !== void 0) result["EnablePrefixForIpv6SourceNat"] = lb.EnablePrefixForIpv6SourceNat;
28338
+ if (lb.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic !== void 0) result["EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic"] = lb.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic;
28339
+ if (lb.IpamPools?.Ipv4IpamPoolId !== void 0) result["Ipv4IpamPoolId"] = lb.IpamPools.Ipv4IpamPoolId;
28340
+ try {
28341
+ const units = (await this.getClient().send(new DescribeCapacityReservationCommand({ LoadBalancerArn: physicalId }))).MinimumLoadBalancerCapacity?.CapacityUnits;
28342
+ if (units !== void 0 && units > 0) result["MinimumLoadBalancerCapacity"] = { CapacityUnits: units };
28343
+ } catch (err) {
28344
+ if (this.isNotFoundError(err)) return void 0;
28345
+ }
28115
28346
  try {
28116
28347
  result["LoadBalancerAttributes"] = ((await this.getClient().send(new DescribeLoadBalancerAttributesCommand({ LoadBalancerArn: physicalId }))).Attributes ?? []).filter((a) => typeof a.Key === "string" && typeof a.Value === "string").map((a) => ({
28117
28348
  Key: a.Key,
@@ -28147,13 +28378,30 @@ var ELBv2Provider = class {
28147
28378
  if (tg.HealthCheckTimeoutSeconds !== void 0) result["HealthCheckTimeoutSeconds"] = tg.HealthCheckTimeoutSeconds;
28148
28379
  if (tg.HealthyThresholdCount !== void 0) result["HealthyThresholdCount"] = tg.HealthyThresholdCount;
28149
28380
  if (tg.UnhealthyThresholdCount !== void 0) result["UnhealthyThresholdCount"] = tg.UnhealthyThresholdCount;
28381
+ if (tg.IpAddressType !== void 0) result["IpAddressType"] = tg.IpAddressType;
28382
+ if (tg.TargetControlPort !== void 0) result["TargetControlPort"] = tg.TargetControlPort;
28150
28383
  const matcher = {};
28151
28384
  if (tg.Matcher?.HttpCode !== void 0) matcher["HttpCode"] = tg.Matcher.HttpCode;
28152
28385
  if (tg.Matcher?.GrpcCode !== void 0) matcher["GrpcCode"] = tg.Matcher.GrpcCode;
28153
28386
  result["Matcher"] = matcher;
28387
+ try {
28388
+ result["TargetGroupAttributes"] = ((await this.getClient().send(new DescribeTargetGroupAttributesCommand({ TargetGroupArn: physicalId }))).Attributes ?? []).filter((a) => typeof a.Key === "string" && typeof a.Value === "string").map((a) => ({
28389
+ Key: a.Key,
28390
+ Value: a.Value
28391
+ })).sort((a, b) => a.Key.localeCompare(b.Key));
28392
+ } catch (err) {
28393
+ if (this.isNotFoundError(err)) return void 0;
28394
+ }
28154
28395
  await this.attachTags(result, physicalId);
28155
28396
  return result;
28156
28397
  }
28398
+ getDriftUnknownPaths(resourceType) {
28399
+ switch (resourceType) {
28400
+ case "AWS::ElasticLoadBalancingV2::LoadBalancer": return ["EnableCapacityReservationProvisionStabilize"];
28401
+ case "AWS::ElasticLoadBalancingV2::TargetGroup": return ["Targets"];
28402
+ default: return [];
28403
+ }
28404
+ }
28157
28405
  async readListener(physicalId) {
28158
28406
  let listener;
28159
28407
  try {
@@ -50256,32 +50504,57 @@ function isIntrinsic(value) {
50256
50504
  return keys.length === 1 && INTRINSIC_KEYS.has(keys[0]);
50257
50505
  }
50258
50506
  /**
50259
- * Strip unchanged and intrinsic-only values from a diff value.
50260
- *
50261
- * Recursively compares `value` against `other` and keeps only the keys whose
50262
- * values actually differ (excluding intrinsic vs resolved mismatches). This
50263
- * produces a minimal diff showing only real changes.
50507
+ * Per-side rendering rule for a diff side that is NOT a plain object pair
50508
+ * (primitives, arrays, intrinsics). The both-plain-objects case is handled
50509
+ * jointly by {@link stripUnchangedValuePair}, which never delegates it here.
50264
50510
  */
50265
50511
  function stripUnchangedValues(value, other) {
50266
50512
  if (value === null || value === void 0 || typeof value !== "object") return value;
50267
50513
  if (Array.isArray(value)) return value;
50268
50514
  if (isIntrinsic(value)) return void 0;
50269
50515
  if (isIntrinsic(other)) return void 0;
50270
- if (other === null || other === void 0 || typeof other !== "object" || Array.isArray(other)) return value;
50271
- const valObj = value;
50272
- const otherObj = other;
50273
- const result = {};
50274
- for (const key of Object.keys(valObj)) {
50275
- const v = valObj[key];
50276
- const o = otherObj[key];
50277
- if (isIntrinsic(v) || isIntrinsic(o)) continue;
50278
- if (JSON.stringify(v) === JSON.stringify(o)) continue;
50279
- if (typeof v === "object" && v !== null && typeof o === "object" && o !== null) {
50280
- const filtered = stripUnchangedValues(v, o);
50281
- if (filtered !== void 0 && JSON.stringify(filtered) !== "{}") result[key] = filtered;
50282
- } else result[key] = v;
50516
+ return value;
50517
+ }
50518
+ function isPlainNonIntrinsicObject(value) {
50519
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !isIntrinsic(value);
50520
+ }
50521
+ /**
50522
+ * Strip unchanged and intrinsic-only keys from BOTH sides of a property
50523
+ * change in ONE walk, so the two rendered sides stay symmetric (issue #1608).
50524
+ *
50525
+ * {@link stripUnchangedValues} runs per side, so a PURE KEY ADDITION pruned
50526
+ * the new side down to the added key while the old side — whose own keys are
50527
+ * all unchanged hit the empty-result fallback and printed the FULL object.
50528
+ * That asymmetry reads as "the whole object is replaced by just the added
50529
+ * key" (i.e. everything else removed), the opposite of what deploy does.
50530
+ * Walking the UNION of keys once yields `old: {}` / `new: {AddedKey: ...}`
50531
+ * for an addition (and the mirror for a removal); the full-value fallback —
50532
+ * kept for sides differing only in key order or intrinsic-valued keys — now
50533
+ * fires only when BOTH sides pruned to nothing, so it can never be one-sided.
50534
+ * Non-object sides (primitives, arrays, intrinsics) keep the existing
50535
+ * per-side semantics via {@link stripUnchangedValues}.
50536
+ */
50537
+ function stripUnchangedValuePair(oldValue, newValue) {
50538
+ if (!isPlainNonIntrinsicObject(oldValue) || !isPlainNonIntrinsicObject(newValue)) return [stripUnchangedValues(oldValue, newValue), stripUnchangedValues(newValue, oldValue)];
50539
+ const oldResult = {};
50540
+ const newResult = {};
50541
+ const keys = [...Object.keys(oldValue), ...Object.keys(newValue).filter((k) => !Object.hasOwn(oldValue, k))];
50542
+ for (const key of keys) {
50543
+ const o = oldValue[key];
50544
+ const n = newValue[key];
50545
+ if (isIntrinsic(o) || isIntrinsic(n)) continue;
50546
+ if (JSON.stringify(o) === JSON.stringify(n)) continue;
50547
+ if (isPlainNonIntrinsicObject(o) && isPlainNonIntrinsicObject(n)) {
50548
+ const [fo, fn] = stripUnchangedValuePair(o, n);
50549
+ if (fo !== void 0 && JSON.stringify(fo) !== "{}") oldResult[key] = fo;
50550
+ if (fn !== void 0 && JSON.stringify(fn) !== "{}") newResult[key] = fn;
50551
+ } else {
50552
+ if (Object.hasOwn(oldValue, key)) oldResult[key] = o;
50553
+ if (Object.hasOwn(newValue, key)) newResult[key] = n;
50554
+ }
50283
50555
  }
50284
- return Object.keys(result).length > 0 ? result : value;
50556
+ if (Object.keys(oldResult).length === 0 && Object.keys(newResult).length === 0) return [oldValue, newValue];
50557
+ return [oldResult, newResult];
50285
50558
  }
50286
50559
  /**
50287
50560
  * Render one side of a property change for the diff output.
@@ -50290,18 +50563,18 @@ function stripUnchangedValues(value, other) {
50290
50563
  * against current state — most commonly a `Ref` / `Fn::GetAtt` to a resource
50291
50564
  * this same deploy will CREATE (the CDK logical-id-churn dance: an
50292
50565
  * `AWS::ApiGateway::Deployment` hash rotation, a `fn.currentVersion` Lambda
50293
- * Version). Rendering that via {@link stripUnchangedValues} collapsed it to
50566
+ * Version). Rendering that via the strip-unchanged pass collapsed it to
50294
50567
  * the literal string `undefined`, which reads as "this property is being
50295
50568
  * removed" (issue #1017). Instead, render the raw intrinsic compactly and —
50296
50569
  * on the NEW side — annotate that the value only exists after the deploy.
50297
- * Everything else keeps the existing strip-unchanged rendering.
50570
+ * Everything else renders the pre-filtered value computed jointly for both
50571
+ * sides by {@link stripUnchangedValuePair}.
50298
50572
  */
50299
- function renderDiffValue(own, opposite, indent, isNewSide) {
50573
+ function renderDiffValue(own, filtered, indent, isNewSide) {
50300
50574
  if (isIntrinsic(own)) {
50301
50575
  const suffix = isNewSide ? " (known after deploy)" : "";
50302
50576
  return `${JSON.stringify(own)}${suffix}`;
50303
50577
  }
50304
- const filtered = stripUnchangedValues(own, opposite);
50305
50578
  return (JSON.stringify(filtered, null, 2) ?? "undefined").replace(/\n/g, `\n${indent}`);
50306
50579
  }
50307
50580
  /**
@@ -50336,8 +50609,9 @@ function renderChangeLines(changes, logFn, ccApiRoutes) {
50336
50609
  const requiresReplace = propChange.requiresReplacement ? " [requires replacement]" : "";
50337
50610
  const propagated = propChange.replacementPropagated ? " [replacement propagated]" : "";
50338
50611
  const indent = " ";
50339
- const oldStr = renderDiffValue(propChange.oldValue, propChange.newValue, indent, false);
50340
- const newStr = renderDiffValue(propChange.newValue, propChange.oldValue, indent, true);
50612
+ const [oldFiltered, newFiltered] = stripUnchangedValuePair(propChange.oldValue, propChange.newValue);
50613
+ const oldStr = renderDiffValue(propChange.oldValue, oldFiltered, indent, false);
50614
+ const newStr = renderDiffValue(propChange.newValue, newFiltered, indent, true);
50341
50615
  logFn(` - ${propChange.path}:${requiresReplace}${propagated}`);
50342
50616
  logFn(` old: ${oldStr}`);
50343
50617
  logFn(` new: ${newStr}`);
@@ -68913,7 +69187,7 @@ function createMigrateCommand() {
68913
69187
  */
68914
69188
  function buildProgram() {
68915
69189
  const program = new Command();
68916
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.280.11");
69190
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.280.13");
68917
69191
  program.hook("preAction", (_thisCommand, actionCommand) => {
68918
69192
  const { profile } = actionCommand.optsWithGlobals();
68919
69193
  if (profile !== void 0) process.env["AWS_PROFILE"] = profile;