@go-to-k/cdkd 0.137.3 → 0.138.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
@@ -55,7 +55,7 @@ import { AddTagsCommand as AddTagsCommand$1, CloudTrailClient, CreateTrailComman
55
55
  import { BatchGetProjectsCommand, CodeBuildClient, CreateProjectCommand, DeleteProjectCommand, ListProjectsCommand, ResourceNotFoundException as ResourceNotFoundException$9, UpdateProjectCommand } from "@aws-sdk/client-codebuild";
56
56
  import { CreateVectorBucketCommand, DeleteIndexCommand, DeleteVectorBucketCommand, GetVectorBucketCommand, ListIndexesCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$18, ListVectorBucketsCommand, S3VectorsClient } from "@aws-sdk/client-s3vectors";
57
57
  import { CreateNamespaceCommand, CreateTableBucketCommand, CreateTableCommand as CreateTableCommand$2, DeleteNamespaceCommand as DeleteNamespaceCommand$1, DeleteTableBucketCommand, DeleteTableCommand as DeleteTableCommand$2, GetTableBucketCommand, GetTableCommand as GetTableCommand$1, ListNamespacesCommand as ListNamespacesCommand$1, ListTableBucketsCommand, ListTablesCommand as ListTablesCommand$1, ListTagsForResourceCommand as ListTagsForResourceCommand$19, NotFoundException as NotFoundException$5, S3TablesClient } from "@aws-sdk/client-s3tables";
58
- import { AttachTrafficSourcesCommand, AutoScalingClient, CreateAutoScalingGroupCommand, DeleteAutoScalingGroupCommand, DeleteLifecycleHookCommand, DeleteNotificationConfigurationCommand, DescribeAutoScalingGroupsCommand, DescribeLifecycleHooksCommand, DescribeNotificationConfigurationsCommand, DescribeTrafficSourcesCommand, DetachTrafficSourcesCommand, DisableMetricsCollectionCommand, EnableMetricsCollectionCommand, PutLifecycleHookCommand, PutNotificationConfigurationCommand, UpdateAutoScalingGroupCommand } from "@aws-sdk/client-auto-scaling";
58
+ import { AttachLoadBalancerTargetGroupsCommand, AttachLoadBalancersCommand, AttachTrafficSourcesCommand, AutoScalingClient, CreateAutoScalingGroupCommand, CreateOrUpdateTagsCommand, DeleteAutoScalingGroupCommand, DeleteLifecycleHookCommand, DeleteNotificationConfigurationCommand, DeleteTagsCommand as DeleteTagsCommand$1, DescribeAutoScalingGroupsCommand, DescribeLifecycleHooksCommand, DescribeNotificationConfigurationsCommand, DescribeTrafficSourcesCommand, DetachLoadBalancerTargetGroupsCommand, DetachLoadBalancersCommand, DetachTrafficSourcesCommand, DisableMetricsCollectionCommand, EnableMetricsCollectionCommand, PutLifecycleHookCommand, PutNotificationConfigurationCommand, UpdateAutoScalingGroupCommand } from "@aws-sdk/client-auto-scaling";
59
59
  import * as readline from "node:readline/promises";
60
60
  import { Document, Pair, Scalar, YAMLMap, YAMLSeq, parse as parse$1, stringify } from "yaml";
61
61
  import { mkdir, mkdtemp } from "node:fs/promises";
@@ -29963,27 +29963,36 @@ var ECRProvider = class {
29963
29963
  * Groups` already provides.
29964
29964
  *
29965
29965
  * Update has narrower coverage than create: AWS does not support modifying
29966
- * `AutoScalingGroupName` (immutable), `Tags` (those go through `CreateOrUpdate
29967
- * Tags` / `DeleteTags`), or attached LB / target-group references (those go
29968
- * through `Attach*` / `Detach*` calls). Those diffs still surface
29969
- * `ResourceUpdateNotSupportedError` so the caller can `cdkd deploy --replace`.
29970
- * The mutable fields handled in-place via `UpdateAutoScalingGroup` include
29971
- * MinSize / MaxSize / DesiredCapacity / VPCZoneIdentifier / HealthCheckType /
29972
- * HealthCheckGracePeriod / DefaultCooldown / Cooldown / NewInstancesProtected
29973
- * FromScaleIn / MaxInstanceLifetime / TerminationPolicies / CapacityRebalance
29974
- * / ServiceLinkedRoleARN / Context / DesiredCapacityType / DefaultInstance
29975
- * Warmup / AvailabilityZones / AvailabilityZoneDistribution / Availability
29976
- * ZoneImpairmentPolicy / SkipZonalShiftValidation / CapacityReservation
29977
- * Specification / InstanceMaintenancePolicy / DeletionProtection / Mixed
29978
- * InstancesPolicy / LaunchTemplate.
29966
+ * `AutoScalingGroupName` (immutable) that diff still surfaces
29967
+ * `ResourceUpdateNotSupportedError` so the caller can `cdkd deploy
29968
+ * --replace`. The mutable fields handled in-place via
29969
+ * `UpdateAutoScalingGroup` include MinSize / MaxSize / DesiredCapacity /
29970
+ * VPCZoneIdentifier / HealthCheckType / HealthCheckGracePeriod /
29971
+ * DefaultCooldown / Cooldown / NewInstancesProtectedFromScaleIn /
29972
+ * MaxInstanceLifetime / TerminationPolicies / CapacityRebalance /
29973
+ * ServiceLinkedRoleARN / Context / DesiredCapacityType /
29974
+ * DefaultInstanceWarmup / AvailabilityZones / AvailabilityZoneDistribution
29975
+ * / AvailabilityZoneImpairmentPolicy / SkipZonalShiftValidation /
29976
+ * CapacityReservationSpecification / InstanceMaintenancePolicy /
29977
+ * DeletionProtection / MixedInstancesPolicy / LaunchTemplate.
29979
29978
  *
29980
29979
  * Sub-shape diffs are applied via dedicated AWS APIs before the main
29981
- * `UpdateAutoScalingGroup` call: `MetricsCollection` →
29982
- * `EnableMetricsCollection` / `DisableMetricsCollection`,
29983
- * `LifecycleHookSpecificationList` → per-entry `PutLifecycleHook` /
29984
- * `DeleteLifecycleHook`, `TrafficSources` → `AttachTrafficSources` /
29985
- * `DetachTrafficSources`, `NotificationConfigurations` → per-topic
29986
- * `PutNotificationConfiguration` / `DeleteNotificationConfiguration`.
29980
+ * `UpdateAutoScalingGroup` call:
29981
+ * - `Tags` → `CreateOrUpdateTags` / `DeleteTags` (#475)
29982
+ * - `LoadBalancerNames` → `AttachLoadBalancers` /
29983
+ * `DetachLoadBalancers` (#476)
29984
+ * - `TargetGroupARNs` `AttachLoadBalancerTargetGroups` /
29985
+ * `DetachLoadBalancerTargetGroups` (#476)
29986
+ * - `MetricsCollection` → `EnableMetricsCollection` /
29987
+ * `DisableMetricsCollection`
29988
+ * - `LifecycleHookSpecificationList` → per-entry `PutLifecycleHook` /
29989
+ * `DeleteLifecycleHook`
29990
+ * - `TrafficSources` → `AttachTrafficSources` /
29991
+ * `DetachTrafficSources`
29992
+ * - `NotificationConfigurations` → per-topic
29993
+ * `PutNotificationConfiguration` /
29994
+ * `DeleteNotificationConfiguration`
29995
+ *
29987
29996
  * Each helper is a no-op when the before/after JSON is identical.
29988
29997
  */
29989
29998
  var ASGProvider = class {
@@ -30091,10 +30100,10 @@ var ASGProvider = class {
30091
30100
  this.logger.debug(`Updating AutoScalingGroup ${logicalId}: ${physicalId}`);
30092
30101
  const stringEq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
30093
30102
  if (!stringEq(properties["AutoScalingGroupName"], previousProperties["AutoScalingGroupName"])) throw new ResourceUpdateNotSupportedError(resourceType, logicalId, "AutoScalingGroupName is immutable on AWS — UpdateAutoScalingGroup does not accept a new name; the name is fixed at creation. Use cdkd deploy --replace to replace the group.");
30094
- if (!stringEq(properties["Tags"] ?? [], previousProperties["Tags"] ?? [])) throw new ResourceUpdateNotSupportedError(resourceType, logicalId, "Tags updates on AWS::AutoScaling::AutoScalingGroup are not yet implemented in cdkd (AWS exposes CreateOrUpdateTags / DeleteTags); use cdkd deploy --replace, or update the tags via AWS console / CLI.");
30095
- if (!stringEq(properties["LoadBalancerNames"] ?? [], previousProperties["LoadBalancerNames"] ?? [])) throw new ResourceUpdateNotSupportedError(resourceType, logicalId, "LoadBalancerNames diffs on AWS::AutoScaling::AutoScalingGroup are not yet implemented in cdkd (AWS exposes AttachLoadBalancers / DetachLoadBalancers); use cdkd deploy --replace.");
30096
- if (!stringEq(properties["TargetGroupARNs"] ?? [], previousProperties["TargetGroupARNs"] ?? [])) throw new ResourceUpdateNotSupportedError(resourceType, logicalId, "TargetGroupARNs diffs on AWS::AutoScaling::AutoScalingGroup are not yet implemented in cdkd (AWS exposes AttachLoadBalancerTargetGroups / DetachLoadBalancerTargetGroups); use cdkd deploy --replace.");
30097
30103
  try {
30104
+ await this.applyTagsDiff(physicalId, properties["Tags"], previousProperties["Tags"]);
30105
+ await this.applyLoadBalancerNamesDiff(physicalId, properties["LoadBalancerNames"], previousProperties["LoadBalancerNames"]);
30106
+ await this.applyTargetGroupArnsDiff(physicalId, properties["TargetGroupARNs"], previousProperties["TargetGroupARNs"]);
30098
30107
  await this.applyMetricsCollectionDiff(physicalId, properties["MetricsCollection"], previousProperties["MetricsCollection"]);
30099
30108
  await this.applyLifecycleHooksDiff(physicalId, properties["LifecycleHookSpecificationList"], previousProperties["LifecycleHookSpecificationList"]);
30100
30109
  await this.applyTrafficSourcesDiff(physicalId, properties["TrafficSources"], previousProperties["TrafficSources"]);
@@ -30354,6 +30363,99 @@ var ASGProvider = class {
30354
30363
  sleep(ms) {
30355
30364
  return new Promise((resolve) => setTimeout(resolve, ms));
30356
30365
  }
30366
+ /**
30367
+ * Diff and apply changes to the ASG's `Tags` property via the
30368
+ * `CreateOrUpdateTags` / `DeleteTags` AWS APIs (#475). CFn Tags shape is
30369
+ * `[{Key, Value, PropagateAtLaunch}]`; AWS Tag input adds `ResourceId`
30370
+ * (= the ASG name) and `ResourceType: 'auto-scaling-group'`.
30371
+ *
30372
+ * Diff semantics:
30373
+ * - Removed keys → `DeleteTags`.
30374
+ * - Added keys → `CreateOrUpdateTags`.
30375
+ * - Modified value or `PropagateAtLaunch` flag → `CreateOrUpdateTags`
30376
+ * (the AWS API upserts by `(ResourceId, ResourceType, Key)` tuple, so
30377
+ * a single upsert call replaces the old value).
30378
+ *
30379
+ * No-op when before/after JSON is identical.
30380
+ */
30381
+ async applyTagsDiff(physicalId, next, prev) {
30382
+ if (JSON.stringify(next ?? []) === JSON.stringify(prev ?? [])) return;
30383
+ const nextEntries = Array.isArray(next) ? next : [];
30384
+ const prevEntries = Array.isArray(prev) ? prev : [];
30385
+ const nextByKey = /* @__PURE__ */ new Map();
30386
+ for (const t of nextEntries) if (t.Key) nextByKey.set(t.Key, t);
30387
+ const prevByKey = /* @__PURE__ */ new Map();
30388
+ for (const t of prevEntries) if (t.Key) prevByKey.set(t.Key, t);
30389
+ const toDelete = [];
30390
+ for (const [key, tag] of prevByKey) if (!nextByKey.has(key)) toDelete.push(tag);
30391
+ if (toDelete.length > 0) await this.getClient().send(new DeleteTagsCommand$1({ Tags: toDelete.map((t) => ({
30392
+ ResourceId: physicalId,
30393
+ ResourceType: "auto-scaling-group",
30394
+ Key: t.Key
30395
+ })) }));
30396
+ const toUpsert = [];
30397
+ for (const [key, tag] of nextByKey) {
30398
+ const before = prevByKey.get(key);
30399
+ if (JSON.stringify(before) === JSON.stringify(tag)) continue;
30400
+ toUpsert.push(tag);
30401
+ }
30402
+ if (toUpsert.length > 0) await this.getClient().send(new CreateOrUpdateTagsCommand({ Tags: toUpsert.map((t) => ({
30403
+ ResourceId: physicalId,
30404
+ ResourceType: "auto-scaling-group",
30405
+ Key: t.Key,
30406
+ ...t.Value !== void 0 && { Value: t.Value },
30407
+ ...t.PropagateAtLaunch !== void 0 && { PropagateAtLaunch: t.PropagateAtLaunch }
30408
+ })) }));
30409
+ }
30410
+ /**
30411
+ * Diff `LoadBalancerNames` (Classic Load Balancers) and issue
30412
+ * `AttachLoadBalancers` / `DetachLoadBalancers` for the delta (#476).
30413
+ * Names are opaque strings; AWS allows N attached LBs per ASG so this
30414
+ * helper batches every add into one Attach call and every remove into
30415
+ * one Detach call. No-op when before/after JSON is identical.
30416
+ */
30417
+ async applyLoadBalancerNamesDiff(physicalId, next, prev) {
30418
+ if (JSON.stringify(next ?? []) === JSON.stringify(prev ?? [])) return;
30419
+ const nextNames = (Array.isArray(next) ? next : []).filter((n) => typeof n === "string");
30420
+ const prevNames = (Array.isArray(prev) ? prev : []).filter((n) => typeof n === "string");
30421
+ const nextSet = new Set(nextNames);
30422
+ const prevSet = new Set(prevNames);
30423
+ const toAttach = nextNames.filter((n) => !prevSet.has(n));
30424
+ const toDetach = prevNames.filter((n) => !nextSet.has(n));
30425
+ if (toDetach.length > 0) await this.getClient().send(new DetachLoadBalancersCommand({
30426
+ AutoScalingGroupName: physicalId,
30427
+ LoadBalancerNames: toDetach
30428
+ }));
30429
+ if (toAttach.length > 0) await this.getClient().send(new AttachLoadBalancersCommand({
30430
+ AutoScalingGroupName: physicalId,
30431
+ LoadBalancerNames: toAttach
30432
+ }));
30433
+ }
30434
+ /**
30435
+ * Diff `TargetGroupARNs` (ALB / NLB target groups) and issue
30436
+ * `AttachLoadBalancerTargetGroups` /
30437
+ * `DetachLoadBalancerTargetGroups` for the delta (#476). Target-group
30438
+ * ARNs are opaque strings; same per-call batching pattern as
30439
+ * `applyLoadBalancerNamesDiff`. No-op when before/after JSON is
30440
+ * identical.
30441
+ */
30442
+ async applyTargetGroupArnsDiff(physicalId, next, prev) {
30443
+ if (JSON.stringify(next ?? []) === JSON.stringify(prev ?? [])) return;
30444
+ const nextArns = (Array.isArray(next) ? next : []).filter((a) => typeof a === "string");
30445
+ const prevArns = (Array.isArray(prev) ? prev : []).filter((a) => typeof a === "string");
30446
+ const nextSet = new Set(nextArns);
30447
+ const prevSet = new Set(prevArns);
30448
+ const toAttach = nextArns.filter((a) => !prevSet.has(a));
30449
+ const toDetach = prevArns.filter((a) => !nextSet.has(a));
30450
+ if (toDetach.length > 0) await this.getClient().send(new DetachLoadBalancerTargetGroupsCommand({
30451
+ AutoScalingGroupName: physicalId,
30452
+ TargetGroupARNs: toDetach
30453
+ }));
30454
+ if (toAttach.length > 0) await this.getClient().send(new AttachLoadBalancerTargetGroupsCommand({
30455
+ AutoScalingGroupName: physicalId,
30456
+ TargetGroupARNs: toAttach
30457
+ }));
30458
+ }
30357
30459
  async applyMetricsCollectionDiff(physicalId, next, prev) {
30358
30460
  if (JSON.stringify(next ?? []) === JSON.stringify(prev ?? [])) return;
30359
30461
  const nextEntries = Array.isArray(next) ? next : [];
@@ -39898,6 +40000,25 @@ function resolveFnSubInvokeArn(arg) {
39898
40000
  };
39899
40001
  }
39900
40002
 
40003
+ //#endregion
40004
+ //#region src/local/intrinsic-utils.ts
40005
+ /**
40006
+ * If `value` is a `{ Ref: <string> }` intrinsic, return the referenced
40007
+ * logical ID. Otherwise return `null`.
40008
+ *
40009
+ * Shared across the `src/local/*` resolvers (route discovery, authorizer
40010
+ * resolution, stage attachment) so future intrinsic-shape extensions
40011
+ * (e.g. accepting `Fn::Sub`-bound Refs in REST v1 ResourceId / ParentId)
40012
+ * land in one place instead of three.
40013
+ */
40014
+ function pickRefLogicalId(value) {
40015
+ if (value && typeof value === "object" && !Array.isArray(value)) {
40016
+ const ref = value["Ref"];
40017
+ if (typeof ref === "string") return ref;
40018
+ }
40019
+ return null;
40020
+ }
40021
+
39901
40022
  //#endregion
39902
40023
  //#region src/local/httpv2-service-integration.ts
39903
40024
  const logger = getLogger();
@@ -40363,7 +40484,7 @@ function discoverRestV1Method(logicalId, resource, template, stackName) {
40363
40484
  const integration = props["Integration"];
40364
40485
  if (!integration) throw new Error(`${stackName}/${logicalId} (AWS::ApiGateway::Method): missing Integration property`);
40365
40486
  const restApiId = props["RestApiId"];
40366
- const restApiLogicalId = pickRefLogicalId$3(restApiId);
40487
+ const restApiLogicalId = pickRefLogicalId(restApiId);
40367
40488
  if (!restApiLogicalId) throw new Error(`${stackName}/${logicalId} (AWS::ApiGateway::Method): RestApiId must be a { Ref: '...' } reference (got ${shortJson$1(restApiId)}).`);
40368
40489
  const resourceId = props["ResourceId"];
40369
40490
  const path = buildRestV1Path(resourceId, restApiLogicalId, template, stackName, logicalId);
@@ -40723,7 +40844,7 @@ function buildRestV1Path(resourceIdIntrinsic, restApiLogicalId, template, stackN
40723
40844
  if (Array.isArray(arg) && arg.length === 2 && arg[1] === "RootResourceId") return "/";
40724
40845
  }
40725
40846
  }
40726
- const resourceLogicalId = pickRefLogicalId$3(resourceIdIntrinsic);
40847
+ const resourceLogicalId = pickRefLogicalId(resourceIdIntrinsic);
40727
40848
  if (!resourceLogicalId) throw new Error(`${stackName}/${methodLogicalId}: ResourceId must be { Ref: '...' } or { 'Fn::GetAtt': [..., 'RootResourceId'] } (got ${shortJson$1(resourceIdIntrinsic)}).`);
40728
40849
  const segments = [];
40729
40850
  const visited = /* @__PURE__ */ new Set();
@@ -40743,7 +40864,7 @@ function buildRestV1Path(resourceIdIntrinsic, restApiLogicalId, template, stackN
40743
40864
  const arg = parentId["Fn::GetAtt"];
40744
40865
  if (Array.isArray(arg) && arg[1] === "RootResourceId") break;
40745
40866
  }
40746
- cursor = pickRefLogicalId$3(parentId) ?? void 0;
40867
+ cursor = pickRefLogicalId(parentId) ?? void 0;
40747
40868
  }
40748
40869
  return "/" + segments.join("/");
40749
40870
  }
@@ -40758,7 +40879,7 @@ function pickRestV1Stage(restApiLogicalId, template) {
40758
40879
  for (const [, resource] of Object.entries(resources)) {
40759
40880
  if (resource.Type !== "AWS::ApiGateway::Stage") continue;
40760
40881
  const props = resource.Properties ?? {};
40761
- if (pickRefLogicalId$3(props["RestApiId"]) === restApiLogicalId) {
40882
+ if (pickRefLogicalId(props["RestApiId"]) === restApiLogicalId) {
40762
40883
  const stageName = props["StageName"];
40763
40884
  if (typeof stageName === "string") return stageName;
40764
40885
  }
@@ -40777,7 +40898,7 @@ function pickRestV1Stage(restApiLogicalId, template) {
40777
40898
  function discoverHttpApiRoute(logicalId, resource, template, stackName) {
40778
40899
  const props = resource.Properties ?? {};
40779
40900
  const apiId = props["ApiId"];
40780
- const apiLogicalId = pickRefLogicalId$3(apiId);
40901
+ const apiLogicalId = pickRefLogicalId(apiId);
40781
40902
  if (!apiLogicalId) throw new Error(`${stackName}/${logicalId} (AWS::ApiGatewayV2::Route): ApiId must be { Ref: '...' } (got ${shortJson$1(apiId)}).`);
40782
40903
  const routeKey = props["RouteKey"];
40783
40904
  if (typeof routeKey !== "string" || routeKey.length === 0) throw new Error(`${stackName}/${logicalId} (AWS::ApiGatewayV2::Route): RouteKey must be a string`);
@@ -40990,11 +41111,11 @@ function parseHttpApiTargetIntegration(target, location) {
40990
41111
  const sep = join[0];
40991
41112
  const parts = join[1];
40992
41113
  if (sep === "/" && parts.length === 2 && parts[0] === "integrations") {
40993
- const ref = pickRefLogicalId$3(parts[1]);
41114
+ const ref = pickRefLogicalId(parts[1]);
40994
41115
  if (ref) return ref;
40995
41116
  }
40996
41117
  if (sep === "" && parts.length === 2 && parts[0] === "integrations/") {
40997
- const ref = pickRefLogicalId$3(parts[1]);
41118
+ const ref = pickRefLogicalId(parts[1]);
40998
41119
  if (ref) return ref;
40999
41120
  }
41000
41121
  }
@@ -41014,7 +41135,7 @@ function parseHttpApiTargetIntegration(target, location) {
41014
41135
  if (m) {
41015
41136
  const bound = bindings[m[1]];
41016
41137
  if (bound !== void 0) {
41017
- const ref = pickRefLogicalId$3(bound);
41138
+ const ref = pickRefLogicalId(bound);
41018
41139
  if (ref) return ref;
41019
41140
  }
41020
41141
  }
@@ -41040,17 +41161,6 @@ function parseRouteKey(routeKey) {
41040
41161
  };
41041
41162
  }
41042
41163
  /**
41043
- * If `value` is a `{ Ref: <string> }` intrinsic, return the referenced
41044
- * logical ID. Otherwise return `null`.
41045
- */
41046
- function pickRefLogicalId$3(value) {
41047
- if (value && typeof value === "object" && !Array.isArray(value)) {
41048
- const ref = value["Ref"];
41049
- if (typeof ref === "string") return ref;
41050
- }
41051
- return null;
41052
- }
41053
- /**
41054
41164
  * Compact JSON for error messages — caps long objects so a malformed
41055
41165
  * intrinsic doesn't dump the whole template into a stderr line.
41056
41166
  */
@@ -41142,7 +41252,7 @@ function collectAuthRoutesForApi(apiLogicalId, template, _stackName) {
41142
41252
  for (const [, resource] of Object.entries(resources)) {
41143
41253
  if (resource.Type !== "AWS::ApiGatewayV2::Route") continue;
41144
41254
  const props = resource.Properties ?? {};
41145
- if (pickRefLogicalId$2(props["ApiId"]) !== apiLogicalId) continue;
41255
+ if (pickRefLogicalId(props["ApiId"]) !== apiLogicalId) continue;
41146
41256
  const authType = props["AuthorizationType"];
41147
41257
  if (authType === void 0) continue;
41148
41258
  const routeKey = props["RouteKey"];
@@ -41194,7 +41304,7 @@ function pickStage$1(apiLogicalId, template) {
41194
41304
  for (const [, resource] of Object.entries(resources)) {
41195
41305
  if (resource.Type !== "AWS::ApiGatewayV2::Stage") continue;
41196
41306
  const props = resource.Properties ?? {};
41197
- if (pickRefLogicalId$2(props["ApiId"]) === apiLogicalId) {
41307
+ if (pickRefLogicalId(props["ApiId"]) === apiLogicalId) {
41198
41308
  const stageName = props["StageName"];
41199
41309
  if (typeof stageName === "string" && stageName.length > 0) return stageName;
41200
41310
  }
@@ -41215,7 +41325,7 @@ function collectRoutesForApi(apiLogicalId, template, stackName) {
41215
41325
  for (const [routeLogicalId, resource] of Object.entries(resources)) {
41216
41326
  if (resource.Type !== "AWS::ApiGatewayV2::Route") continue;
41217
41327
  const props = resource.Properties ?? {};
41218
- if (pickRefLogicalId$2(props["ApiId"]) !== apiLogicalId) continue;
41328
+ if (pickRefLogicalId(props["ApiId"]) !== apiLogicalId) continue;
41219
41329
  const declaredAt = `${stackName}/${routeLogicalId}`;
41220
41330
  const routeKey = props["RouteKey"];
41221
41331
  if (typeof routeKey !== "string" || routeKey.length === 0) throw new Error(`${declaredAt}: RouteKey must be a non-empty string.`);
@@ -41263,11 +41373,11 @@ function parseRouteTarget(target, location) {
41263
41373
  const sep = join[0];
41264
41374
  const parts = join[1];
41265
41375
  if (sep === "/" && parts.length === 2 && parts[0] === "integrations") {
41266
- const ref = pickRefLogicalId$2(parts[1]);
41376
+ const ref = pickRefLogicalId(parts[1]);
41267
41377
  if (ref) return ref;
41268
41378
  }
41269
41379
  if (sep === "" && parts.length === 2 && parts[0] === "integrations/") {
41270
- const ref = pickRefLogicalId$2(parts[1]);
41380
+ const ref = pickRefLogicalId(parts[1]);
41271
41381
  if (ref) return ref;
41272
41382
  }
41273
41383
  }
@@ -41288,7 +41398,7 @@ function parseRouteTarget(target, location) {
41288
41398
  if (m) {
41289
41399
  const bound = bindings[m[1]];
41290
41400
  if (bound !== void 0) {
41291
- const ref = pickRefLogicalId$2(bound);
41401
+ const ref = pickRefLogicalId(bound);
41292
41402
  if (ref) return ref;
41293
41403
  }
41294
41404
  }
@@ -41297,13 +41407,6 @@ function parseRouteTarget(target, location) {
41297
41407
  }
41298
41408
  throw new Error(`${location}: Target must be 'integrations/<id>' literal, Fn::Join with the documented shapes, or Fn::Sub with an 'integrations/\${...}' template.`);
41299
41409
  }
41300
- function pickRefLogicalId$2(value) {
41301
- if (value && typeof value === "object" && !Array.isArray(value)) {
41302
- const ref = value["Ref"];
41303
- if (typeof ref === "string") return ref;
41304
- }
41305
- return null;
41306
- }
41307
41410
  function readApiCdkPath(logicalId, template) {
41308
41411
  const resource = template.Resources?.[logicalId];
41309
41412
  if (!resource) return void 0;
@@ -45119,11 +45222,22 @@ function parseCognitoIssuer(issuer) {
45119
45222
  };
45120
45223
  }
45121
45224
  /**
45122
- * Pull a string out of a {Ref} / literal entry under `ProviderARNs`.
45123
- * CDK's CognitoUserPoolsAuthorizer emits a literal array of `Fn::GetAtt:
45124
- * [<UserPool>, 'Arn']` entries we accept both. The `location` argument
45125
- * carries the full `<stack>/<authorizer>.ProviderARNs[<idx>]` path so the
45126
- * error names the offending entry exactly.
45225
+ * Pull a string out of a literal / `Fn::GetAtt` entry under `ProviderARNs`.
45226
+ *
45227
+ * CDK's `apigateway.CognitoUserPoolsAuthorizer` emits a `Fn::GetAtt:
45228
+ * [<UserPool>, 'Arn']` reference, which is the canonical shape any user
45229
+ * who writes `new CognitoUserPoolsAuthorizer(this, 'auth', { cognitoUserPools: [pool] })`
45230
+ * ends up with (#470). Without `--from-state` we cannot resolve the
45231
+ * deployed pool ARN, so we synthesize an obviously-unreachable placeholder
45232
+ * pointing at a non-existent pool id — the JWKS fetch will fail and
45233
+ * cognito-jwt.ts's pass-through fallback (PR #234) admits every JWT
45234
+ * without signature verification. The warn log names the affected
45235
+ * authorizer + the recommended explicit `providerArns` workaround so
45236
+ * developers who DO want real verification know how to switch over.
45237
+ *
45238
+ * The `location` argument carries the full
45239
+ * `<stack>/<authorizer>.ProviderARNs[<idx>]` path so the warn / error
45240
+ * names the offending entry exactly.
45127
45241
  */
45128
45242
  function pickStringFromArn(value, location) {
45129
45243
  if (typeof value === "string") return value;
@@ -45131,7 +45245,11 @@ function pickStringFromArn(value, location) {
45131
45245
  const obj = value;
45132
45246
  if ("Fn::GetAtt" in obj) {
45133
45247
  const arg = obj["Fn::GetAtt"];
45134
- if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && arg[1] === "Arn") throw new RouteDiscoveryError(`${location}: uses Fn::GetAtt against logical ID '${arg[0]}'. cdkd local start-api needs the literal ARN string to derive the JWKS URL — set the user pool ARN explicitly via 'authorizer.providerArns' on the CDK construct, or upgrade to JWT (HTTP v2) which encodes the pool in the Issuer URL.`);
45248
+ if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && arg[1] === "Arn") {
45249
+ const logicalId = arg[0];
45250
+ getLogger().warn(`${location}: uses Fn::GetAtt against logical ID '${logicalId}'. cdkd local start-api cannot resolve the deployed user pool ARN — synthesizing an unreachable placeholder so JWKS pass-through admits every token. For real signature verification, set 'providerArns: [pool.userPoolArn]' explicitly on the CDK construct.`);
45251
+ return `arn:aws:cognito-idp:us-east-1:000000000000:userpool/us-east-1_cdkdplaceholder${logicalId}`;
45252
+ }
45135
45253
  }
45136
45254
  }
45137
45255
  throw new RouteDiscoveryError(`${location}: must be a literal string (got ${shortJson(value)}).`);
@@ -45218,7 +45336,7 @@ function detectRestV1Authorizer(methodResource, methodLogicalId, stack) {
45218
45336
  declaredAt: `${stack.stackName}/${methodLogicalId}`
45219
45337
  };
45220
45338
  const authorizerId = props["AuthorizerId"];
45221
- const refLogicalId = pickRefLogicalId$1(authorizerId);
45339
+ const refLogicalId = pickRefLogicalId(authorizerId);
45222
45340
  if (!refLogicalId) throw new RouteDiscoveryError(`${stack.stackName}/${methodLogicalId}: AuthorizationType='${stringifyValue(authType)}' but AuthorizerId is missing or not a {Ref:...}.`);
45223
45341
  return resolveRestV1Authorizer(refLogicalId, stack.template, stack.stackName, `${stack.stackName}/${methodLogicalId}`);
45224
45342
  }
@@ -45227,18 +45345,11 @@ function detectHttpApiAuthorizer(routeResource, routeLogicalId, stack) {
45227
45345
  const authType = props["AuthorizationType"];
45228
45346
  if (authType === void 0 || authType === "NONE") return void 0;
45229
45347
  const authorizerId = props["AuthorizerId"];
45230
- const refLogicalId = pickRefLogicalId$1(authorizerId);
45348
+ const refLogicalId = pickRefLogicalId(authorizerId);
45231
45349
  if (!refLogicalId) throw new RouteDiscoveryError(`${stack.stackName}/${routeLogicalId}: AuthorizationType='${stringifyValue(authType)}' but AuthorizerId is missing or not a {Ref:...}.`);
45232
45350
  const scopesRaw = props["AuthorizationScopes"];
45233
45351
  return resolveHttpApiAuthorizer(refLogicalId, Array.isArray(scopesRaw) ? scopesRaw.filter((s) => typeof s === "string") : void 0, stack.template, stack.stackName, `${stack.stackName}/${routeLogicalId}`);
45234
45352
  }
45235
- function pickRefLogicalId$1(value) {
45236
- if (value && typeof value === "object" && !Array.isArray(value)) {
45237
- const ref = value["Ref"];
45238
- if (typeof ref === "string") return ref;
45239
- }
45240
- return null;
45241
- }
45242
45353
  function shortJson(value) {
45243
45354
  try {
45244
45355
  const s = JSON.stringify(value);
@@ -47809,19 +47920,6 @@ function attachStageContext(routes, stageMap) {
47809
47920
  route.stage = stage.stageName;
47810
47921
  }
47811
47922
  }
47812
- /**
47813
- * If `value` is a `{ Ref: <string> }` intrinsic, return the referenced
47814
- * logical ID. Otherwise return `null`. (Duplicated structurally from
47815
- * `route-discovery.ts` — both modules walk the template independently
47816
- * and shouldn't grow a coupling for a 5-line helper.)
47817
- */
47818
- function pickRefLogicalId(value) {
47819
- if (value && typeof value === "object" && !Array.isArray(value)) {
47820
- const ref = value["Ref"];
47821
- if (typeof ref === "string") return ref;
47822
- }
47823
- return null;
47824
- }
47825
47923
 
47826
47924
  //#endregion
47827
47925
  //#region src/local/file-watcher.ts
@@ -54164,7 +54262,7 @@ function reorderArgs(argv) {
54164
54262
  */
54165
54263
  async function main() {
54166
54264
  const program = new Command();
54167
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.137.3");
54265
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.138.0");
54168
54266
  program.addCommand(createBootstrapCommand());
54169
54267
  program.addCommand(createSynthCommand());
54170
54268
  program.addCommand(createListCommand());