@go-to-k/cdkd 0.162.0 → 0.162.1

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
@@ -51153,6 +51153,140 @@ function pickStringArray(value) {
51153
51153
  return out;
51154
51154
  }
51155
51155
  /**
51156
+ * Build a `fnUrlLogicalId → CorsConfig` map by tracing CloudFront →
51157
+ * Function URL chains in the template (issue #646).
51158
+ *
51159
+ * Production-correct CDK pattern: Function URL fronted by a CloudFront
51160
+ * Distribution where CORS is declared on the CloudFront
51161
+ * `ResponseHeadersPolicy` (NOT on the Function URL itself). Without this
51162
+ * helper, `cdkd local start-api` sees `Cors: null` on the Function URL
51163
+ * and emits no preflight headers — even though the CDK code correctly
51164
+ * declares the allowed origins on the CloudFront side.
51165
+ *
51166
+ * Detection: an `AWS::CloudFront::Distribution` whose `Origins[].DomainName`
51167
+ * matches the canonical CDK 2.x shape
51168
+ * `Fn::Select[2, Fn::Split['/', Fn::GetAtt[<FnUrlLogicalId>, 'FunctionUrl']]]`
51169
+ * is the chain marker. For each such origin, we walk every cache behavior
51170
+ * (`DefaultCacheBehavior` + `CacheBehaviors[]`), resolve their
51171
+ * `ResponseHeadersPolicyId: { Ref: <RhpLogicalId> }` to the
51172
+ * `AWS::CloudFront::ResponseHeadersPolicy` resource, and extract its
51173
+ * `Properties.ResponseHeadersPolicyConfig.CorsConfig`.
51174
+ *
51175
+ * Schema mapping (CloudFront → internal `CorsConfig`):
51176
+ *
51177
+ * AccessControlAllowOrigins.Items → AllowOrigins
51178
+ * AccessControlAllowMethods.Items → AllowMethods
51179
+ * AccessControlAllowHeaders.Items → AllowHeaders
51180
+ * AccessControlExposeHeaders.Items → ExposeHeaders
51181
+ * AccessControlMaxAgeSec → MaxAge
51182
+ * AccessControlAllowCredentials → AllowCredentials
51183
+ * (OriginOverride is ignored — cdkd has only one config slot)
51184
+ *
51185
+ * Multiple distributions fronting the same Function URL: last write
51186
+ * wins (rare in practice). Per-path CORS via `CacheBehaviors[]` is
51187
+ * NOT supported in v1 — the `DefaultCacheBehavior`'s policy applies
51188
+ * to all paths.
51189
+ */
51190
+ function buildCorsConfigFromCloudFrontChain(template) {
51191
+ const out = /* @__PURE__ */ new Map();
51192
+ const resources = template.Resources ?? {};
51193
+ for (const [, resource] of Object.entries(resources)) {
51194
+ if (resource.Type !== "AWS::CloudFront::Distribution") continue;
51195
+ const distConfig = (resource.Properties ?? {})["DistributionConfig"];
51196
+ if (!distConfig || typeof distConfig !== "object") continue;
51197
+ const dc = distConfig;
51198
+ const origins = Array.isArray(dc["Origins"]) ? dc["Origins"] : [];
51199
+ for (const origin of origins) {
51200
+ if (!origin || typeof origin !== "object") continue;
51201
+ const fnUrlLogicalId = pickFnUrlLogicalIdFromOriginDomainName(origin["DomainName"]);
51202
+ if (!fnUrlLogicalId) continue;
51203
+ const cacheBehaviors = [dc["DefaultCacheBehavior"], ...Array.isArray(dc["CacheBehaviors"]) ? dc["CacheBehaviors"] : []];
51204
+ for (const behavior of cacheBehaviors) {
51205
+ if (!behavior || typeof behavior !== "object") continue;
51206
+ const rhpId = pickRhpRefLogicalId(behavior["ResponseHeadersPolicyId"]);
51207
+ if (!rhpId) continue;
51208
+ const rhpResource = resources[rhpId];
51209
+ if (!rhpResource || rhpResource.Type !== "AWS::CloudFront::ResponseHeadersPolicy") continue;
51210
+ const rhpConfig = (rhpResource.Properties ?? {})["ResponseHeadersPolicyConfig"];
51211
+ if (!rhpConfig || typeof rhpConfig !== "object") continue;
51212
+ const corsConfig = rhpConfig["CorsConfig"];
51213
+ if (!corsConfig || typeof corsConfig !== "object" || Array.isArray(corsConfig)) continue;
51214
+ const parsed = parseCloudFrontCorsConfig(corsConfig);
51215
+ if (parsed) out.set(fnUrlLogicalId, parsed);
51216
+ }
51217
+ }
51218
+ }
51219
+ return out;
51220
+ }
51221
+ /**
51222
+ * Detect the canonical CDK 2.x `DomainName` shape that points a
51223
+ * CloudFront Origin at a Function URL:
51224
+ * {Fn::Select: [2, {Fn::Split: ['/', {Fn::GetAtt: [<id>, 'FunctionUrl']}]}]}
51225
+ * Returns the Function URL's logical ID, or undefined if the shape
51226
+ * doesn't match.
51227
+ */
51228
+ function pickFnUrlLogicalIdFromOriginDomainName(value) {
51229
+ if (!value || typeof value !== "object") return void 0;
51230
+ const sel = value["Fn::Select"];
51231
+ if (!Array.isArray(sel) || sel.length !== 2 || sel[0] !== 2) return void 0;
51232
+ const split = sel[1];
51233
+ if (!split || typeof split !== "object") return void 0;
51234
+ const splitArgs = split["Fn::Split"];
51235
+ if (!Array.isArray(splitArgs) || splitArgs.length !== 2 || splitArgs[0] !== "/") return void 0;
51236
+ const getAtt = splitArgs[1];
51237
+ if (!getAtt || typeof getAtt !== "object") return void 0;
51238
+ const ga = getAtt["Fn::GetAtt"];
51239
+ if (!Array.isArray(ga) || ga.length !== 2 || typeof ga[0] !== "string" || ga[1] !== "FunctionUrl") return;
51240
+ return ga[0];
51241
+ }
51242
+ /**
51243
+ * Unwrap a `ResponseHeadersPolicyId` value to its referenced logical
51244
+ * ID. CDK 2.x synthesizes this as `{ Ref: <id> }`. Returns undefined
51245
+ * for the AWS-managed-policy ID form (literal UUID string) since
51246
+ * cdkd can't fetch those — and for any non-Ref shape.
51247
+ */
51248
+ function pickRhpRefLogicalId(value) {
51249
+ if (!value || typeof value !== "object") return void 0;
51250
+ const ref = value["Ref"];
51251
+ if (typeof ref !== "string" || ref.length === 0) return void 0;
51252
+ return ref;
51253
+ }
51254
+ /**
51255
+ * Parse a CloudFront `ResponseHeadersPolicyConfig.CorsConfig` block
51256
+ * into the internal `CorsConfig` shape. Schema differs from Function
51257
+ * URL / HTTP API v2 (`AccessControl*` prefix + nested `Items` wrapper);
51258
+ * see `buildCorsConfigFromCloudFrontChain` JSDoc for the field mapping.
51259
+ *
51260
+ * Returns undefined when every value-bearing field is missing.
51261
+ */
51262
+ function parseCloudFrontCorsConfig(raw) {
51263
+ const allowOrigins = pickItemsStringArray(raw["AccessControlAllowOrigins"]);
51264
+ const allowMethods = pickItemsStringArray(raw["AccessControlAllowMethods"]);
51265
+ const allowHeaders = pickItemsStringArray(raw["AccessControlAllowHeaders"]);
51266
+ const exposeHeaders = pickItemsStringArray(raw["AccessControlExposeHeaders"]);
51267
+ const maxAgeRaw = raw["AccessControlMaxAgeSec"];
51268
+ const allowCreds = raw["AccessControlAllowCredentials"];
51269
+ if (allowOrigins.length === 0 && allowMethods.length === 0 && allowHeaders.length === 0 && exposeHeaders.length === 0 && maxAgeRaw === void 0 && allowCreds === void 0) return;
51270
+ const config = {
51271
+ AllowOrigins: allowOrigins,
51272
+ AllowMethods: allowMethods,
51273
+ AllowHeaders: allowHeaders,
51274
+ ExposeHeaders: exposeHeaders
51275
+ };
51276
+ if (typeof maxAgeRaw === "number" && Number.isFinite(maxAgeRaw)) config.MaxAge = Math.trunc(maxAgeRaw);
51277
+ if (typeof allowCreds === "boolean") config.AllowCredentials = allowCreds;
51278
+ return config;
51279
+ }
51280
+ /**
51281
+ * CloudFront `AccessControl*Origins/Methods/Headers` use a nested
51282
+ * `Items: string[]` wrapper. Unwrap to a plain `string[]`.
51283
+ */
51284
+ function pickItemsStringArray(value) {
51285
+ if (!value || typeof value !== "object") return [];
51286
+ const items = value["Items"];
51287
+ return pickStringArray(items);
51288
+ }
51289
+ /**
51156
51290
  * Try to match an OPTIONS preflight request against the given CORS
51157
51291
  * config. Returns the canonical response when every check passes;
51158
51292
  * `null` when the request didn't satisfy AllowOrigins / AllowMethods /
@@ -54528,8 +54662,10 @@ async function localStartApiCommand(target, options) {
54528
54662
  }
54529
54663
  const corsConfigByApiId = /* @__PURE__ */ new Map();
54530
54664
  for (const stack of targetStacks) {
54531
- const m = buildCorsConfigByApiId(stack.template);
54532
- for (const [k, v] of m) corsConfigByApiId.set(k, v);
54665
+ const fromCloudFront = buildCorsConfigFromCloudFrontChain(stack.template);
54666
+ for (const [k, v] of fromCloudFront) corsConfigByApiId.set(k, v);
54667
+ const direct = buildCorsConfigByApiId(stack.template);
54668
+ for (const [k, v] of direct) corsConfigByApiId.set(k, v);
54533
54669
  }
54534
54670
  const stateByStack = options.fromState || isCfnFlagPresent(options) ? await loadStateForRoutedStacks(targetStacks, routes, routesWithAuth, options) : /* @__PURE__ */ new Map();
54535
54671
  const lambdaIds = uniqueLambdaIds(routes, routesWithAuth, webSocketApis);
@@ -60065,7 +60201,7 @@ function reorderArgs(argv) {
60065
60201
  */
60066
60202
  async function main() {
60067
60203
  const program = new Command();
60068
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.162.0");
60204
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.162.1");
60069
60205
  program.addCommand(createBootstrapCommand());
60070
60206
  program.addCommand(createSynthCommand());
60071
60207
  program.addCommand(createListCommand());