@go-to-k/cdkd 0.222.1 → 0.223.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1553,7 +1553,7 @@ const FLUSH_INTERVAL_MS = 2e3;
1553
1553
  const FLUSH_EVENT_THRESHOLD = 50;
1554
1554
  /** Build-time cdkd version, with a dev fallback for non-built contexts. */
1555
1555
  function getCdkdVersion() {
1556
- return "0.222.1";
1556
+ return "0.223.0";
1557
1557
  }
1558
1558
  /**
1559
1559
  * Generate a time-sortable unique run id, e.g.
@@ -18384,6 +18384,88 @@ var CloudFrontDistributionProvider = class {
18384
18384
  }
18385
18385
  }
18386
18386
  /**
18387
+ * Read the AWS-current `DistributionConfig` for drift detection.
18388
+ *
18389
+ * Calls the read-only `GetDistributionConfigCommand` and inverts the
18390
+ * provider's own `convertToSdkFormat` (the CFn-shape → SDK-shape
18391
+ * `{ Quantity, Items }` mapping the create / update path applies) back
18392
+ * into the CloudFormation `DistributionConfig` shape that cdkd state
18393
+ * stores — so the drift comparator sees the same structure on both
18394
+ * the deploy-time observed snapshot and a later drift read.
18395
+ *
18396
+ * The comparator only descends into keys present in cdkd state (or the
18397
+ * union of state + AWS when an observed baseline exists), so this does
18398
+ * not need to surface every AWS-injected field; it returns the same
18399
+ * `DistributionConfig` / `Tags` keys the provider manages.
18400
+ *
18401
+ * `CallerReference` (a create-time idempotency token AWS echoes back
18402
+ * but CDK never templates) is dropped so it cannot fire phantom drift.
18403
+ * The remaining write-only / never-readable sub-fields are declared in
18404
+ * `getDriftUnknownPaths`.
18405
+ */
18406
+ async readCurrentState(physicalId, _logicalId, resourceType, _properties) {
18407
+ if (resourceType !== "AWS::CloudFront::Distribution") return void 0;
18408
+ let config;
18409
+ try {
18410
+ config = (await this.cloudFrontClient.send(new GetDistributionConfigCommand({ Id: physicalId }))).DistributionConfig;
18411
+ } catch (error) {
18412
+ if (error instanceof NoSuchDistribution) return void 0;
18413
+ throw error;
18414
+ }
18415
+ if (!config) return void 0;
18416
+ const result = { DistributionConfig: this.convertToCfnFormat(config) };
18417
+ try {
18418
+ const arn = (await this.cloudFrontClient.send(new GetDistributionCommand({ Id: physicalId }))).Distribution?.ARN;
18419
+ if (arn) {
18420
+ const cfnTags = ((await this.cloudFrontClient.send(new ListTagsForResourceCommand$3({ Resource: arn }))).Tags?.Items ?? []).filter((t) => typeof t.Key === "string" && !t.Key.startsWith("aws:cdk:")).map((t) => ({
18421
+ Key: t.Key,
18422
+ Value: t.Value ?? ""
18423
+ }));
18424
+ if (cfnTags.length > 0) result["Tags"] = cfnTags;
18425
+ }
18426
+ } catch (error) {
18427
+ this.logger.debug(`Tag read for CloudFront Distribution ${physicalId} failed during drift read: ${error instanceof Error ? error.message : String(error)}`);
18428
+ }
18429
+ return result;
18430
+ }
18431
+ /**
18432
+ * State property paths cdkd cannot read back faithfully from AWS, so
18433
+ * the drift comparator skips them rather than firing a guaranteed
18434
+ * false positive every run (mirrors the Lambda `Code.S3*` precedent).
18435
+ *
18436
+ * - `DistributionConfig.CallerReference`: a create-time idempotency
18437
+ * token. cdkd generates it internally (`<ts>-<logicalId>-<rand>`);
18438
+ * AWS echoes it back, but it is never templated by CDK, so comparing
18439
+ * it would diff cdkd's generated value on every run. `convertToCfnFormat`
18440
+ * already drops it from the AWS side; declaring it here also excludes
18441
+ * it from the observed-baseline side.
18442
+ * - `DistributionConfig.Logging.Bucket`: CloudFront normalizes the S3
18443
+ * logging bucket to its `<bucket>.s3.amazonaws.com` regional domain on
18444
+ * read, which never matches the bare bucket domain a template may
18445
+ * carry; treat it as drift-unknown to avoid a guaranteed mismatch.
18446
+ * - `DistributionConfig.OriginGroups`: each origin group carries its own
18447
+ * inner `{ Quantity, Items }` wrappers (`Members`,
18448
+ * `FailoverCriteria.StatusCodes`) that `convertToCfnFormat` does not yet
18449
+ * unwrap symmetrically (only the top-level OriginGroups list is unwrapped;
18450
+ * there is no `revertOriginGroup` pass, and `convertToSdkFormat` likewise
18451
+ * does not descend into them). Against the observed baseline both sides go
18452
+ * through the same `readCurrentState` so this never false-positives on a
18453
+ * normal deploy, but against the `properties`-fallback baseline (older
18454
+ * state without `observedProperties`) the raw `{ Quantity, Items }` shape
18455
+ * would diff the bare-array template form. Suppress it until a dedicated
18456
+ * fix lands a `revertOriginGroup` + a real OriginGroups integ fixture
18457
+ * (there is no OriginGroups fixture to verify the inner shape today). See
18458
+ * the PR #871 review thread.
18459
+ */
18460
+ getDriftUnknownPaths(resourceType) {
18461
+ if (resourceType !== "AWS::CloudFront::Distribution") return [];
18462
+ return [
18463
+ "DistributionConfig.CallerReference",
18464
+ "DistributionConfig.Logging.Bucket",
18465
+ "DistributionConfig.OriginGroups"
18466
+ ];
18467
+ }
18468
+ /**
18387
18469
  * Get resource attribute (for Fn::GetAtt resolution)
18388
18470
  */
18389
18471
  async getAttribute(physicalId, _resourceType, attributeName) {
@@ -18462,6 +18544,105 @@ var CloudFrontDistributionProvider = class {
18462
18544
  return result;
18463
18545
  }
18464
18546
  /**
18547
+ * Invert {@link convertToSdkFormat}: map an AWS-returned SDK-shape
18548
+ * `DistributionConfig` ({@link GetDistributionConfigCommand} output) back
18549
+ * into the CloudFormation `DistributionConfig` shape cdkd state stores.
18550
+ *
18551
+ * The inversion is the mirror image of the create / update conversion:
18552
+ * every `{ Quantity, Items }` wrapper becomes its bare `Items` array
18553
+ * (top-level list fields + the nested cache-behavior / origin fields),
18554
+ * and `CallerReference` is dropped (a create-time idempotency token CDK
18555
+ * never templates — see `getDriftUnknownPaths`).
18556
+ *
18557
+ * Lossiness note: the conversion is NOT perfectly lossless because
18558
+ * `convertToSdkFormat` injects SDK-required defaults the CFn template may
18559
+ * omit (`Comment: ''`, `Logging.{Enabled,IncludeCookies,Prefix}`,
18560
+ * `CustomOriginConfig.HTTP{,S}Port`). Those defaults are preserved on the
18561
+ * inverted side, which is correct for the drift comparator: the
18562
+ * deploy-time observed baseline (also produced by THIS method) carries the
18563
+ * same defaults, so they compare equal. They would only matter against the
18564
+ * `properties` fallback baseline (resources with no observedProperties),
18565
+ * where an AWS-injected default the template omitted is legitimately a
18566
+ * key cdkd never set — but the comparator's state-keys-only walk in that
18567
+ * mode never descends into a key absent from the template, so the extra
18568
+ * defaults cannot fire false drift there either.
18569
+ */
18570
+ convertToCfnFormat(config) {
18571
+ const result = { ...config };
18572
+ delete result["CallerReference"];
18573
+ for (const field of QUANTITY_ITEM_FIELDS) if (result[field] !== void 0) result[field] = this.unwrapQuantity(result[field]);
18574
+ if (result["DefaultCacheBehavior"] && typeof result["DefaultCacheBehavior"] === "object") result["DefaultCacheBehavior"] = this.revertCacheBehavior(result["DefaultCacheBehavior"]);
18575
+ if (Array.isArray(result["CacheBehaviors"])) result["CacheBehaviors"] = result["CacheBehaviors"].map((cb) => this.revertCacheBehavior(cb));
18576
+ if (Array.isArray(result["Origins"])) result["Origins"] = result["Origins"].map((origin) => this.revertOrigin(origin));
18577
+ return result;
18578
+ }
18579
+ /**
18580
+ * Inverse of {@link wrapWithQuantity}: a `{ Quantity, Items }` object
18581
+ * becomes its bare `Items` array; anything else is returned unchanged.
18582
+ */
18583
+ unwrapQuantity(value) {
18584
+ if (value && typeof value === "object" && !Array.isArray(value)) {
18585
+ const obj = value;
18586
+ if (Array.isArray(obj["Items"])) return obj["Items"];
18587
+ }
18588
+ return value;
18589
+ }
18590
+ /**
18591
+ * Inverse of {@link convertCacheBehavior}: unwrap the Quantity + Items
18592
+ * fields nested inside a CacheBehavior back to bare arrays.
18593
+ */
18594
+ revertCacheBehavior(behavior) {
18595
+ const result = { ...behavior };
18596
+ if (result["AllowedMethods"] && typeof result["AllowedMethods"] === "object") {
18597
+ const allowed = result["AllowedMethods"];
18598
+ if (allowed["CachedMethods"] !== void 0 && result["CachedMethods"] === void 0) {
18599
+ result["CachedMethods"] = allowed["CachedMethods"];
18600
+ const allowedCopy = { ...allowed };
18601
+ delete allowedCopy["CachedMethods"];
18602
+ result["AllowedMethods"] = allowedCopy;
18603
+ }
18604
+ }
18605
+ for (const fieldPath of CACHE_BEHAVIOR_QUANTITY_FIELDS) {
18606
+ const parts = fieldPath.split(".");
18607
+ this.unwrapQuantityAtPath(result, parts);
18608
+ }
18609
+ return result;
18610
+ }
18611
+ /**
18612
+ * Inverse of {@link convertOrigin}: unwrap CustomHeaders and
18613
+ * CustomOriginConfig.OriginSslProtocols back to bare arrays.
18614
+ */
18615
+ revertOrigin(origin) {
18616
+ const result = { ...origin };
18617
+ if (result["CustomHeaders"] !== void 0) result["CustomHeaders"] = this.unwrapQuantity(result["CustomHeaders"]);
18618
+ if (result["CustomOriginConfig"] && typeof result["CustomOriginConfig"] === "object") {
18619
+ const customOriginConfig = { ...result["CustomOriginConfig"] };
18620
+ if (customOriginConfig["OriginSslProtocols"] !== void 0) customOriginConfig["OriginSslProtocols"] = this.unwrapQuantity(customOriginConfig["OriginSslProtocols"]);
18621
+ result["CustomOriginConfig"] = customOriginConfig;
18622
+ }
18623
+ return result;
18624
+ }
18625
+ /**
18626
+ * Inverse of {@link applyQuantityAtPath}: unwrap a `{ Quantity, Items }`
18627
+ * value at a nested path (e.g. "ForwardedValues.Headers") back to a bare
18628
+ * array.
18629
+ */
18630
+ unwrapQuantityAtPath(obj, path) {
18631
+ if (path.length === 0) return;
18632
+ if (path.length === 1) {
18633
+ const key = path[0];
18634
+ if (obj[key] !== void 0) obj[key] = this.unwrapQuantity(obj[key]);
18635
+ return;
18636
+ }
18637
+ const [head, ...rest] = path;
18638
+ const headKey = head;
18639
+ if (obj[headKey] && typeof obj[headKey] === "object") {
18640
+ const nested = { ...obj[headKey] };
18641
+ obj[headKey] = nested;
18642
+ this.unwrapQuantityAtPath(nested, rest);
18643
+ }
18644
+ }
18645
+ /**
18465
18646
  * Wrap a value with the Quantity + Items pattern if needed.
18466
18647
  *
18467
18648
  * Handles three cases:
@@ -54198,7 +54379,7 @@ function reorderArgs(argv) {
54198
54379
  async function main() {
54199
54380
  installPipeCloseHandler();
54200
54381
  const program = new Command();
54201
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.222.1");
54382
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.223.0");
54202
54383
  program.addCommand(createBootstrapCommand());
54203
54384
  program.addCommand(createSynthCommand());
54204
54385
  program.addCommand(createListCommand());