@go-to-k/cdkd 0.280.25 → 0.280.26

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-DOjLU2Et.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-Sb8KbTlA.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-Do4MnkQh.js";
4
+ import { t as ASGProvider } from "./asg-provider-BJsjMRaA.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, 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";
34
+ import { AddTagsCommand, CreateListenerCommand, CreateLoadBalancerCommand, CreateTargetGroupCommand, DeleteListenerCommand, DeleteLoadBalancerCommand, DeleteTargetGroupCommand, DeregisterTargetsCommand, DescribeCapacityReservationCommand, DescribeListenerAttributesCommand, DescribeListenersCommand, DescribeLoadBalancerAttributesCommand, DescribeLoadBalancersCommand, DescribeTagsCommand, DescribeTargetGroupAttributesCommand, DescribeTargetGroupsCommand, DescribeTargetHealthCommand, 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";
@@ -28759,8 +28759,8 @@ var ELBv2Provider = class {
28759
28759
  }));
28760
28760
  this.logger.debug(`Applied ${tgAttrDiff.length} TargetGroupAttributes change(s) for ${logicalId}`);
28761
28761
  }
28762
- const newTargets = this.convertTargets(properties["Targets"]);
28763
- const oldTargets = this.convertTargets(previousProperties["Targets"]);
28762
+ const newTargets = this.convertTargets(properties["Targets"], properties["Port"]);
28763
+ const oldTargets = this.convertTargets(previousProperties["Targets"], properties["Port"]);
28764
28764
  const targetKey = (t) => JSON.stringify([
28765
28765
  t.Id,
28766
28766
  t.Port ?? null,
@@ -29039,8 +29039,21 @@ var ELBv2Provider = class {
29039
29039
  * `Port` is numeric-coerced (CFn templates may carry it as a string).
29040
29040
  * Returns `[]` for an absent / non-array value so callers can branch on
29041
29041
  * `.length`.
29042
- */
29043
- convertTargets(raw) {
29042
+ *
29043
+ * `groupPort` defaults an OMITTED `Port` to the target group's own port,
29044
+ * which is what AWS substitutes when a target is registered without one.
29045
+ * This is load-bearing for the update diff, not cosmetic: `targetKey` below
29046
+ * keys on `Port ?? null`, so without the default a template-shaped
29047
+ * `[{Id}]` and an AWS-readback-shaped `[{Id, Port: 80}]` describe the SAME
29048
+ * live target under two different keys — and the diff then registers the
29049
+ * "new" one (a no-op, it lands on the group port) and DEREGISTERS the live
29050
+ * one. `cdkd drift --revert` hits exactly that, since it hands `update()`
29051
+ * the AWS snapshot as the previous side and a template-shaped desired side.
29052
+ * Absent for a `lambda` target group (no port), where both sides stay
29053
+ * undefined and therefore still key identically.
29054
+ */
29055
+ convertTargets(raw, groupPort) {
29056
+ const defaultPort = groupPort === void 0 || groupPort === null || Number.isNaN(Number(groupPort)) ? void 0 : Number(groupPort);
29044
29057
  if (!Array.isArray(raw)) return [];
29045
29058
  const out = [];
29046
29059
  for (const entry of raw) {
@@ -29049,9 +29062,10 @@ var ELBv2Provider = class {
29049
29062
  continue;
29050
29063
  }
29051
29064
  const e = entry;
29065
+ const port = e.Port !== void 0 ? Number(e.Port) : defaultPort;
29052
29066
  out.push({
29053
29067
  Id: e.Id,
29054
- ...e.Port !== void 0 && { Port: Number(e.Port) },
29068
+ ...port !== void 0 && { Port: port },
29055
29069
  ...e.AvailabilityZone !== void 0 && { AvailabilityZone: e.AvailabilityZone }
29056
29070
  });
29057
29071
  }
@@ -29213,16 +29227,129 @@ var ELBv2Provider = class {
29213
29227
  } catch (err) {
29214
29228
  if (this.isNotFoundError(err)) return void 0;
29215
29229
  }
29230
+ await this.attachRegisteredTargets(result, physicalId);
29216
29231
  await this.attachTags(result, physicalId);
29217
29232
  return result;
29218
29233
  }
29219
- getDriftUnknownPaths(resourceType) {
29234
+ /**
29235
+ * Read the target group's REGISTERED targets back in CFn `Targets` shape via
29236
+ * `DescribeTargetHealth` (issue
29237
+ * [#1620](https://github.com/go-to-k/cdkd/issues/1620)).
29238
+ *
29239
+ * Two things make this safe to compare, and both were the reason `Targets`
29240
+ * sat in {@link getDriftUnknownPaths} until now:
29241
+ *
29242
+ * - **Order.** AWS does not guarantee a readback order for the target list,
29243
+ * and the drift comparator compares arrays positionally. The type now
29244
+ * declares `Targets` in {@link getDriftUnorderedPaths}, which sorts the
29245
+ * list on BOTH comparison sides — so a reorder is not drift.
29246
+ * - **Draining.** Deregistration is asynchronous: a just-removed target
29247
+ * keeps reporting for minutes with `TargetHealth.State === 'draining'`.
29248
+ * Including one would freeze it into the deploy-time `observedProperties`
29249
+ * snapshot and produce PERMANENT phantom drift against every later read,
29250
+ * once it finally disappears. `draining` means "being removed", so the
29251
+ * registered set deliberately excludes it. Every other state (`initial`
29252
+ * right after `RegisterTargets`, `unused` for a target with no listener,
29253
+ * `unhealthy`, `unavailable`) IS a registered target and is included —
29254
+ * health is not registration.
29255
+ *
29256
+ * Two AWS-SUBSTITUTED values are dropped, because echoing back a value the
29257
+ * template never wrote is phantom drift against a `properties`-fallback
29258
+ * baseline — and, worse, a DESTRUCTIVE one: `updateTargetGroup` keys its
29259
+ * register / deregister diff on the whole `(Id, Port, AvailabilityZone)`
29260
+ * tuple, so one extra member makes the SAME live target read as a different
29261
+ * one and `--revert` deregisters it.
29262
+ *
29263
+ * - `Port` when it equals the target group's own port — what AWS
29264
+ * substitutes for a target registered without one. (`convertTargets`
29265
+ * closes the same gap from the other direction, so a template that DOES
29266
+ * spell the port out still keys identically.)
29267
+ * - `AvailabilityZone` unless the group is `ip`-typed and AWS reported
29268
+ * something other than its `all` default. CFn only accepts the member for
29269
+ * `ip` targets, and `DeregisterTargets` rejects it on an instance group.
29270
+ *
29271
+ * Known bound: if `DescribeTargetHealth` were to return an empty list in the
29272
+ * window right after `RegisterTargets`, the deploy-time snapshot would
29273
+ * freeze `Targets: []` against a template that declares some. Not observed —
29274
+ * `RegisterTargets` is synchronous and the integ's post-deploy drift run
29275
+ * sees all three fixture targets — and left unguarded deliberately, since
29276
+ * treating an empty readback as unreadable would also blind a legitimately
29277
+ * emptied target group.
29278
+ *
29279
+ * On error the key is left ABSENT rather than emitted empty. That is not
29280
+ * silent: against an observed baseline that HOLDS targets, an absent key
29281
+ * still reports them as drifted. It is the honest option available — the
29282
+ * read failed, so the live set is unknown — and it keeps `--revert` from
29283
+ * acting on a fabricated empty list.
29284
+ */
29285
+ async attachRegisteredTargets(result, targetGroupArn) {
29286
+ try {
29287
+ const resp = await this.getClient().send(new DescribeTargetHealthCommand({ TargetGroupArn: targetGroupArn }));
29288
+ const groupPort = typeof result["Port"] === "number" ? result["Port"] : void 0;
29289
+ const isIpTargetGroup = result["TargetType"] === "ip";
29290
+ const targets = [];
29291
+ for (const desc of resp.TargetHealthDescriptions ?? []) {
29292
+ if (desc.TargetHealth?.State === "draining") continue;
29293
+ const id = desc.Target?.Id;
29294
+ if (typeof id !== "string" || id.length === 0) continue;
29295
+ const port = desc.Target?.Port;
29296
+ const az = desc.Target?.AvailabilityZone;
29297
+ targets.push({
29298
+ Id: id,
29299
+ ...port !== void 0 && port !== groupPort && { Port: port },
29300
+ ...isIpTargetGroup && az !== void 0 && az !== "all" && { AvailabilityZone: az }
29301
+ });
29302
+ }
29303
+ result["Targets"] = targets;
29304
+ } catch (err) {
29305
+ this.logger.debug(`ELBv2 DescribeTargetHealth(${targetGroupArn}) failed: ${err instanceof Error ? err.message : String(err)}`);
29306
+ }
29307
+ }
29308
+ /**
29309
+ * `Targets` is scoped PER RESOURCE (issue
29310
+ * [#1602](https://github.com/go-to-k/cdkd/issues/1602)'s seam), not switched
29311
+ * off for the type: it is compared only for a target group whose TEMPLATE
29312
+ * declares `Targets`.
29313
+ *
29314
+ * The reason is the issue [#1498](https://github.com/go-to-k/cdkd/issues/1498)
29315
+ * class. A target group fronting an ECS service or an ASG declares NO
29316
+ * `Targets` — the sibling resource registers them, and it keeps
29317
+ * re-registering as it scales. Comparing an undeclared target list would
29318
+ * therefore report drift on every scale event of an untouched stack, and
29319
+ * `--revert` would DEREGISTER the tasks the service just placed. The
29320
+ * `undeclaredEmptyObservedKeys` guard only covers the case where the capture
29321
+ * happened to be EMPTY, which for a redeployed running service it is not.
29322
+ *
29323
+ * An absent properties bag falls back to COMPARING, per the method's
29324
+ * contract — hiding real drift is the worse failure, and the only caller
29325
+ * that omits the bag today is not `cdkd drift`.
29326
+ */
29327
+ getDriftUnknownPaths(resourceType, properties) {
29220
29328
  switch (resourceType) {
29221
29329
  case "AWS::ElasticLoadBalancingV2::LoadBalancer": return ["EnableCapacityReservationProvisionStabilize"];
29222
- case "AWS::ElasticLoadBalancingV2::TargetGroup": return ["Targets"];
29330
+ case "AWS::ElasticLoadBalancingV2::TargetGroup":
29331
+ if (properties !== void 0 && Object.keys(properties).length > 0 && properties["Targets"] === void 0) return ["Targets"];
29332
+ return [];
29223
29333
  default: return [];
29224
29334
  }
29225
29335
  }
29336
+ /**
29337
+ * `Targets` is a semantically UNORDERED set: `RegisterTargets` /
29338
+ * `DeregisterTargets` are set operations (the provider's own update path
29339
+ * key-diffs them rather than comparing positions), and `DescribeTargetHealth`
29340
+ * documents no ordering guarantee. Declared here rather than sorted inside
29341
+ * {@link attachRegisteredTargets} so the sort applies to BOTH comparison
29342
+ * sides — see {@link ResourceProvider.getDriftUnorderedPaths}, whose header
29343
+ * records why one-sided sorting manufactures drift on the
29344
+ * `properties`-fallback baseline.
29345
+ *
29346
+ * `TargetGroupAttributes` needs no declaration: it is a `{Key, Value}` list,
29347
+ * which the shared tag-list canonicalizer already sorts on both sides.
29348
+ */
29349
+ getDriftUnorderedPaths(resourceType) {
29350
+ if (resourceType !== "AWS::ElasticLoadBalancingV2::TargetGroup") return [];
29351
+ return ["Targets"];
29352
+ }
29226
29353
  async readListener(physicalId) {
29227
29354
  let listener;
29228
29355
  try {
@@ -51985,10 +52112,11 @@ function createDiffCommand() {
51985
52112
  * the sibling cdk-real-drift (cdkrd) tool, which hit the same false-positive
51986
52113
  * classes against the same kind of AWS-snapshot baseline.
51987
52114
  *
51988
- * Plain-string arrays are NOT canonicalized by the two heuristic passes above,
51989
- * because a scalar list can be order-significant. But several CFn inputs are
51990
- * semantically unordered sets of plain strings (FSx `WindowsConfiguration.Aliases`,
51991
- * `...SelfManagedActiveDirectoryConfiguration.DnsIps`, ...), so
52115
+ * Plain-string arrays and NON-tag object arrays are NOT canonicalized by the
52116
+ * two heuristic passes above, because either can be order-significant. But
52117
+ * several CFn inputs are semantically unordered sets of plain strings (FSx
52118
+ * `WindowsConfiguration.Aliases`) or of objects (ELBv2
52119
+ * `TargetGroup.Targets`), so
51992
52120
  * {@link canonicalizeUnorderedArraysAtPaths} sorts them at an explicit,
51993
52121
  * provider-declared path list only — an opt-in seam mirroring
51994
52122
  * `getDriftUnknownPaths` (see `ResourceProvider.getDriftUnorderedPaths`).
@@ -52052,10 +52180,11 @@ function matchesPathPrefix(path, entries) {
52052
52180
  return false;
52053
52181
  }
52054
52182
  /**
52055
- * Sort plain-string arrays found at one of the provider-declared `paths`, and
52056
- * ONLY there. Unlike the two heuristic passes above, this one is opt-in per
52057
- * resource type via `ResourceProvider.getDriftUnorderedPaths` a scalar list
52058
- * is order-significant unless the provider says otherwise.
52183
+ * Sort plain-string arrays AND object arrays found at one of the
52184
+ * provider-declared `paths`, and ONLY there. Unlike the two heuristic passes
52185
+ * above, this one is opt-in per resource type via
52186
+ * `ResourceProvider.getDriftUnorderedPaths` — a list is order-significant
52187
+ * unless the provider says otherwise.
52059
52188
  *
52060
52189
  * Paths use dot-notation for nested keys and are matched by the shared
52061
52190
  * {@link matchesPathPrefix} rule, the same one `getDriftUnknownPaths` uses. So
@@ -52073,21 +52202,68 @@ function matchesPathPrefix(path, entries) {
52073
52202
  * is strictly more permissive; nothing that matches for ignore-paths fails to
52074
52203
  * match here.
52075
52204
  *
52076
- * Only arrays whose every element is a plain string are sorted, so a
52077
- * mis-declared path can never reorder object or mixed-type elements. Nested
52078
- * arrays are also left alone: an array element that is itself an array is not
52079
- * descended into, so `{P: [['b','a']]}` never has its INNER list sorted by a
52080
- * `'P'` declaration (no CFn shape in tree is an array-of-arrays, and sorting
52081
- * one would contradict the plain-string-elements-only rule).
52205
+ * TWO element shapes are sorted, each only when the array is HOMOGENEOUS in it:
52206
+ *
52207
+ * 1. every element a plain string sorted lexically;
52208
+ * 2. every element a plain object sorted by {@link canonicalJson}, a
52209
+ * key-order-independent serialization (issue
52210
+ * [#1620](https://github.com/go-to-k/cdkd/issues/1620)). Key order must not
52211
+ * participate: AWS's readback order for an object's own keys is no more
52212
+ * guaranteed than its order for the list, so sorting on raw
52213
+ * `JSON.stringify` would make the comparison depend on it and reintroduce
52214
+ * the phantom drift this pass exists to remove. `ElasticLoadBalancingV2::
52215
+ * TargetGroup.Targets` is the first consumer.
52216
+ *
52217
+ * A MIXED array (strings and objects, or anything else) is left untouched, so
52218
+ * a mis-declared path can never reorder a heterogeneous list. Nested arrays are
52219
+ * also left alone: an array element that is itself an array is not descended
52220
+ * into, so `{P: [['b','a']]}` never has its INNER list sorted by a `'P'`
52221
+ * declaration. That exclusion is a deliberate decision rather than an
52222
+ * inherited accident — no CFn shape in tree is an array-of-arrays, and an
52223
+ * inner list's own order-significance is a separate question from its
52224
+ * container's, which the single declared path cannot express.
52082
52225
  */
52083
52226
  function canonicalizeUnorderedArraysAtPaths(v, paths) {
52084
52227
  if (paths.length === 0) return v;
52085
52228
  return walkUnordered(v, "", paths);
52086
52229
  }
52230
+ /**
52231
+ * Serialize a value with every object's keys emitted in sorted order, so two
52232
+ * structurally equal objects produce the same string regardless of the key
52233
+ * order they were built in. Used ONLY as a sort key — the returned string is
52234
+ * never compared for equality by the drift comparator, which keeps its own
52235
+ * `deepEqual`.
52236
+ *
52237
+ * Keys are emitted `JSON.stringify`-quoted so a key containing `:` or `,`
52238
+ * cannot forge a different object's serialization, and `undefined`-valued
52239
+ * keys are SKIPPED exactly as `JSON.stringify` drops them — one side of the
52240
+ * comparison arrives via a raw SDK response (where an absent member is
52241
+ * `undefined`) and the other via JSON round-tripped state (where it is simply
52242
+ * gone), so without the skip the same target would key two different ways.
52243
+ */
52244
+ function canonicalJson(v) {
52245
+ if (Array.isArray(v)) return `[${v.map(canonicalJson).join(",")}]`;
52246
+ if (v && typeof v === "object") {
52247
+ const bag = v;
52248
+ return `{${Object.keys(bag).filter((k) => bag[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonicalJson(bag[k])}`).join(",")}}`;
52249
+ }
52250
+ return JSON.stringify(v) ?? "null";
52251
+ }
52252
+ const isPlainObjectElement = (el) => el !== null && typeof el === "object" && !Array.isArray(el);
52087
52253
  function walkUnordered(v, path, paths) {
52088
52254
  if (Array.isArray(v)) {
52089
52255
  const mapped = v.map((el) => Array.isArray(el) ? el : walkUnordered(el, path, paths));
52090
- if (mapped.length > 1 && matchesPathPrefix(path, paths) && mapped.every((el) => typeof el === "string")) return [...mapped].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
52256
+ if (mapped.length > 1 && matchesPathPrefix(path, paths)) {
52257
+ if (mapped.every((el) => typeof el === "string")) return [...mapped].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
52258
+ if (mapped.every(isPlainObjectElement)) {
52259
+ const keyed = mapped.map((el) => ({
52260
+ el,
52261
+ key: canonicalJson(el)
52262
+ }));
52263
+ keyed.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
52264
+ return keyed.map((k) => k.el);
52265
+ }
52266
+ }
52091
52267
  return mapped;
52092
52268
  }
52093
52269
  if (v && typeof v === "object") {
@@ -52158,10 +52334,12 @@ function walkUnordered(v, path, paths) {
52158
52334
  * `'VpcConfig.SubnetIds'` excludes only that leaf.
52159
52335
  *
52160
52336
  * `options.unorderedPaths` is supplied by the provider (via
52161
- * `getDriftUnorderedPaths`) for plain-string arrays that are semantically
52162
- * UNORDERED sets e.g. FSx `WindowsConfiguration.Aliases`. Those are sorted on
52163
- * BOTH sides before comparison so an AWS-side reorder is not phantom drift.
52164
- * Path matching uses the same prefix rule as `ignorePaths`.
52337
+ * `getDriftUnorderedPaths`) for arrays that are semantically UNORDERED sets —
52338
+ * of plain strings (FSx `WindowsConfiguration.Aliases`) or of OBJECTS (ELBv2
52339
+ * `TargetGroup.Targets`, issue #1620, ordered by a key-order-independent
52340
+ * canonical serialization). Those are sorted on BOTH sides before comparison
52341
+ * so an AWS-side reorder is not phantom drift. Path matching uses the same
52342
+ * prefix rule as `ignorePaths`.
52165
52343
  */
52166
52344
  function calculateResourceDrift(stateProperties, awsProperties, options) {
52167
52345
  const drifts = [];
@@ -70417,7 +70595,7 @@ function createMigrateCommand() {
70417
70595
  */
70418
70596
  function buildProgram() {
70419
70597
  const program = new Command();
70420
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.280.25");
70598
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.280.26");
70421
70599
  program.hook("preAction", (_thisCommand, actionCommand) => {
70422
70600
  const { profile } = actionCommand.optsWithGlobals();
70423
70601
  if (profile !== void 0) process.env["AWS_PROFILE"] = profile;