@go-to-k/cdkd 0.52.0 → 0.54.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
@@ -22622,11 +22622,13 @@ import {
22622
22622
  DeleteStageCommand,
22623
22623
  GetStageCommand,
22624
22624
  PutMethodCommand,
22625
+ UpdateMethodCommand,
22625
22626
  DeleteMethodCommand,
22626
22627
  GetMethodCommand,
22627
22628
  PutIntegrationCommand,
22628
22629
  PutMethodResponseCommand,
22629
22630
  CreateAuthorizerCommand,
22631
+ UpdateAuthorizerCommand,
22630
22632
  DeleteAuthorizerCommand,
22631
22633
  GetAuthorizerCommand,
22632
22634
  TagResourceCommand as TagResourceCommand10,
@@ -22713,7 +22715,13 @@ var ApiGatewayProvider = class _ApiGatewayProvider {
22713
22715
  case "AWS::ApiGateway::Account":
22714
22716
  return this.updateAccount(logicalId, physicalId, resourceType, properties);
22715
22717
  case "AWS::ApiGateway::Authorizer":
22716
- return this.updateAuthorizer(logicalId, physicalId, resourceType);
22718
+ return this.updateAuthorizer(
22719
+ logicalId,
22720
+ physicalId,
22721
+ resourceType,
22722
+ properties,
22723
+ previousProperties
22724
+ );
22717
22725
  case "AWS::ApiGateway::Resource":
22718
22726
  return this.updateResource(
22719
22727
  logicalId,
@@ -22733,7 +22741,13 @@ var ApiGatewayProvider = class _ApiGatewayProvider {
22733
22741
  previousProperties
22734
22742
  );
22735
22743
  case "AWS::ApiGateway::Method":
22736
- return this.updateMethod(logicalId, physicalId);
22744
+ return this.updateMethod(
22745
+ logicalId,
22746
+ physicalId,
22747
+ resourceType,
22748
+ properties,
22749
+ previousProperties
22750
+ );
22737
22751
  default:
22738
22752
  throw new ProvisioningError(
22739
22753
  `Unsupported resource type: ${resourceType}`,
@@ -22963,22 +22977,94 @@ var ApiGatewayProvider = class _ApiGatewayProvider {
22963
22977
  }
22964
22978
  }
22965
22979
  /**
22966
- * Update an API Gateway Authorizer.
22980
+ * Update an API Gateway Authorizer via `UpdateAuthorizerCommand` (RFC 6902
22981
+ * JSON Patch operations).
22982
+ *
22983
+ * Mutable fields (per AWS API Gateway PATCH operations docs):
22984
+ * `/name`, `/authType`, `/authorizerUri`, `/authorizerCredentials`,
22985
+ * `/identitySource`, `/identityValidationExpression`,
22986
+ * `/authorizerResultTtlInSeconds`, `/providerARNs`.
22967
22987
  *
22968
- * AWS exposes `UpdateAuthorizer` (PATCH) but cdkd does not yet plumb the
22969
- * patch-operations builder through. Authorizers are recreated by the
22970
- * deploy engine's immutable-property replacement path. `cdkd drift
22971
- * --revert` surfaces a clear "use --replace or re-deploy" message
22972
- * instead of silently no-op'ing the revert.
22988
+ * `Type` and `RestApiId` are immutable (the deploy engine's replacement
22989
+ * path handles those changes via DELETE + CREATE before this method is
22990
+ * called).
22991
+ *
22992
+ * The `gate on !== undefined` pattern (NOT truthy) is load-bearing for
22993
+ * `cdkd drift --revert`: an empty placeholder coming from
22994
+ * `readCurrentStateAuthorizer` (e.g. `IdentitySource: ''` on a Cognito
22995
+ * authorizer) MUST reach AWS as `replace /<field> ''` so a console-side
22996
+ * change away from "" is actually cleared on revert. A truthy gate would
22997
+ * silently drop the empty placeholder and the next drift run would
22998
+ * re-detect the same divergence forever.
22973
22999
  */
22974
- updateAuthorizer(logicalId, _physicalId, _resourceType) {
22975
- return Promise.reject(
22976
- new ResourceUpdateNotSupportedError(
22977
- "AWS::ApiGateway::Authorizer",
23000
+ async updateAuthorizer(logicalId, physicalId, resourceType, properties, previousProperties) {
23001
+ this.logger.debug(`Updating API Gateway Authorizer ${logicalId}: ${physicalId}`);
23002
+ const restApiId = properties["RestApiId"];
23003
+ if (!restApiId) {
23004
+ throw new ProvisioningError(
23005
+ `RestApiId is required to update API Gateway Authorizer ${logicalId}`,
23006
+ resourceType,
22978
23007
  logicalId,
22979
- "API Gateway Authorizer updates are not yet implemented in cdkd; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
22980
- )
22981
- );
23008
+ physicalId
23009
+ );
23010
+ }
23011
+ const patchOperations = [];
23012
+ const primitiveFields = [
23013
+ { key: "Name", path: "/name" },
23014
+ { key: "AuthType", path: "/authType" },
23015
+ { key: "AuthorizerUri", path: "/authorizerUri" },
23016
+ { key: "AuthorizerCredentials", path: "/authorizerCredentials" },
23017
+ { key: "IdentitySource", path: "/identitySource" },
23018
+ { key: "IdentityValidationExpression", path: "/identityValidationExpression" },
23019
+ { key: "AuthorizerResultTtlInSeconds", path: "/authorizerResultTtlInSeconds" }
23020
+ ];
23021
+ for (const { key, path } of primitiveFields) {
23022
+ const newVal = properties[key];
23023
+ const prevVal = previousProperties[key];
23024
+ if (newVal !== prevVal) {
23025
+ patchOperations.push({
23026
+ op: "replace",
23027
+ path,
23028
+ value: newVal !== void 0 ? String(newVal) : ""
23029
+ });
23030
+ }
23031
+ }
23032
+ const newArns = properties["ProviderARNs"];
23033
+ const prevArns = previousProperties["ProviderARNs"];
23034
+ const arnsChanged = (newArns?.length ?? 0) !== (prevArns?.length ?? 0) || (newArns ?? []).some((a, i) => a !== prevArns?.[i]);
23035
+ if (arnsChanged) {
23036
+ patchOperations.push({
23037
+ op: "replace",
23038
+ path: "/providerARNs",
23039
+ value: (newArns ?? []).join(",")
23040
+ });
23041
+ }
23042
+ if (patchOperations.length === 0) {
23043
+ this.logger.debug(`No changes detected for API Gateway Authorizer ${logicalId}`);
23044
+ return { physicalId, wasReplaced: false };
23045
+ }
23046
+ try {
23047
+ await this.apiGatewayClient.send(
23048
+ new UpdateAuthorizerCommand({
23049
+ restApiId,
23050
+ authorizerId: physicalId,
23051
+ patchOperations
23052
+ })
23053
+ );
23054
+ this.logger.debug(
23055
+ `Successfully updated API Gateway Authorizer ${logicalId} (${patchOperations.length} patch ops)`
23056
+ );
23057
+ return { physicalId, wasReplaced: false };
23058
+ } catch (error) {
23059
+ const cause = error instanceof Error ? error : void 0;
23060
+ throw new ProvisioningError(
23061
+ `Failed to update API Gateway Authorizer ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
23062
+ resourceType,
23063
+ logicalId,
23064
+ physicalId,
23065
+ cause
23066
+ );
23067
+ }
22982
23068
  }
22983
23069
  /**
22984
23070
  * Delete an API Gateway Authorizer
@@ -23524,22 +23610,106 @@ var ApiGatewayProvider = class _ApiGatewayProvider {
23524
23610
  }
23525
23611
  }
23526
23612
  /**
23527
- * Update an API Gateway Method.
23613
+ * Update an API Gateway Method via `UpdateMethodCommand` (RFC 6902 JSON
23614
+ * Patch operations).
23615
+ *
23616
+ * Mutable top-level fields:
23617
+ * `/authorizationType`, `/authorizerId`, `/apiKeyRequired`,
23618
+ * `/operationName`, `/requestValidatorId`.
23528
23619
  *
23529
- * AWS exposes `UpdateMethod` (PATCH) but cdkd does not yet plumb the
23530
- * patch-operations builder through. Methods are recreated by the deploy
23531
- * engine's immutable-property replacement path. `cdkd drift --revert`
23532
- * surfaces a clear "use --replace or re-deploy" message instead of
23533
- * silently no-op'ing the revert.
23620
+ * Map fields (`RequestParameters`, `RequestModels`) emit per-key
23621
+ * `add` / `remove` / `replace` ops with paths like
23622
+ * `/requestParameters/method.request.querystring.foo`
23623
+ * `/requestModels/application~1json` (slashes escaped per RFC 6901).
23624
+ *
23625
+ * `HttpMethod`, `ResourceId`, `RestApiId` are immutable (replacement
23626
+ * layer handles them via DELETE + CREATE).
23627
+ *
23628
+ * `Integration` and `MethodResponses` are NOT touched here — they are
23629
+ * separate API Gateway sub-resources (`UpdateIntegration` /
23630
+ * `UpdateMethodResponse`) and the cdkd `create()` path treats them as
23631
+ * inline children of the Method. Round-tripping their structurally-
23632
+ * incomplete `{}` placeholders through `updateMethod` would require
23633
+ * destroying / recreating the integration; that work is deferred and
23634
+ * the empty placeholders are blocked from reaching AWS by the
23635
+ * `!== undefined` gate plus the explicit "ignore Integration /
23636
+ * MethodResponses" comment in this method body.
23637
+ *
23638
+ * The `gate on !== undefined` pattern (NOT truthy) is load-bearing for
23639
+ * `cdkd drift --revert`: `ApiKeyRequired: false` and empty-string
23640
+ * placeholders must reach AWS as a real `replace` op so a console-
23641
+ * side toggle is actually cleared on revert.
23534
23642
  */
23535
- updateMethod(logicalId, _physicalId) {
23536
- return Promise.reject(
23537
- new ResourceUpdateNotSupportedError(
23538
- "AWS::ApiGateway::Method",
23643
+ async updateMethod(logicalId, physicalId, resourceType, properties, previousProperties) {
23644
+ this.logger.debug(`Updating API Gateway Method ${logicalId}: ${physicalId}`);
23645
+ const parts = physicalId.split("|");
23646
+ if (parts.length !== 3) {
23647
+ throw new ProvisioningError(
23648
+ `Invalid physicalId format for API Gateway Method ${logicalId}: expected "restApiId|resourceId|httpMethod", got "${physicalId}"`,
23649
+ resourceType,
23539
23650
  logicalId,
23540
- "API Gateway Method updates are not yet implemented in cdkd; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
23541
- )
23651
+ physicalId
23652
+ );
23653
+ }
23654
+ const [restApiId, resourceId, httpMethod] = parts;
23655
+ const patchOperations = [];
23656
+ const primitiveFields = [
23657
+ { key: "AuthorizationType", path: "/authorizationType" },
23658
+ { key: "AuthorizerId", path: "/authorizerId" },
23659
+ { key: "ApiKeyRequired", path: "/apiKeyRequired" },
23660
+ { key: "OperationName", path: "/operationName" },
23661
+ { key: "RequestValidatorId", path: "/requestValidatorId" }
23662
+ ];
23663
+ for (const { key, path } of primitiveFields) {
23664
+ const newVal = properties[key];
23665
+ const prevVal = previousProperties[key];
23666
+ if (newVal !== prevVal) {
23667
+ patchOperations.push({
23668
+ op: "replace",
23669
+ path,
23670
+ value: newVal !== void 0 ? String(newVal) : ""
23671
+ });
23672
+ }
23673
+ }
23674
+ appendMapPatchOps(
23675
+ patchOperations,
23676
+ "/requestParameters",
23677
+ properties["RequestParameters"] ?? {},
23678
+ previousProperties["RequestParameters"] ?? {}
23542
23679
  );
23680
+ appendMapPatchOps(
23681
+ patchOperations,
23682
+ "/requestModels",
23683
+ properties["RequestModels"] ?? {},
23684
+ previousProperties["RequestModels"] ?? {}
23685
+ );
23686
+ if (patchOperations.length === 0) {
23687
+ this.logger.debug(`No changes detected for API Gateway Method ${logicalId}`);
23688
+ return { physicalId, wasReplaced: false };
23689
+ }
23690
+ try {
23691
+ await this.apiGatewayClient.send(
23692
+ new UpdateMethodCommand({
23693
+ restApiId,
23694
+ resourceId,
23695
+ httpMethod,
23696
+ patchOperations
23697
+ })
23698
+ );
23699
+ this.logger.debug(
23700
+ `Successfully updated API Gateway Method ${logicalId} (${patchOperations.length} patch ops)`
23701
+ );
23702
+ return { physicalId, wasReplaced: false };
23703
+ } catch (error) {
23704
+ const cause = error instanceof Error ? error : void 0;
23705
+ throw new ProvisioningError(
23706
+ `Failed to update API Gateway Method ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
23707
+ resourceType,
23708
+ logicalId,
23709
+ physicalId,
23710
+ cause
23711
+ );
23712
+ }
23543
23713
  }
23544
23714
  /**
23545
23715
  * Delete an API Gateway Method
@@ -23866,6 +24036,23 @@ var ApiGatewayProvider = class _ApiGatewayProvider {
23866
24036
  return null;
23867
24037
  }
23868
24038
  };
24039
+ function appendMapPatchOps(ops, basePath, next, prev) {
24040
+ const escape = (k) => k.replace(/~/g, "~0").replace(/\//g, "~1");
24041
+ for (const [key, val] of Object.entries(next)) {
24042
+ const path = `${basePath}/${escape(key)}`;
24043
+ const stringValue = String(val);
24044
+ if (!(key in prev)) {
24045
+ ops.push({ op: "add", path, value: stringValue });
24046
+ } else if (String(prev[key]) !== stringValue) {
24047
+ ops.push({ op: "replace", path, value: stringValue });
24048
+ }
24049
+ }
24050
+ for (const key of Object.keys(prev)) {
24051
+ if (!(key in next)) {
24052
+ ops.push({ op: "remove", path: `${basePath}/${escape(key)}` });
24053
+ }
24054
+ }
24055
+ }
23869
24056
 
23870
24057
  // src/provisioning/providers/apigatewayv2-provider.ts
23871
24058
  import {
@@ -31673,8 +31860,10 @@ var ElastiCacheProvider = class {
31673
31860
  import {
31674
31861
  ServiceDiscoveryClient,
31675
31862
  CreatePrivateDnsNamespaceCommand,
31863
+ UpdatePrivateDnsNamespaceCommand,
31676
31864
  DeleteNamespaceCommand,
31677
31865
  CreateServiceCommand as CreateServiceCommand2,
31866
+ UpdateServiceCommand as UpdateServiceCommand2,
31678
31867
  DeleteServiceCommand as DeleteServiceCommand2,
31679
31868
  GetNamespaceCommand,
31680
31869
  GetOperationCommand,
@@ -31736,12 +31925,18 @@ var ServiceDiscoveryProvider = class {
31736
31925
  );
31737
31926
  }
31738
31927
  }
31739
- update(logicalId, physicalId, resourceType, _properties, _previousProperties) {
31928
+ update(logicalId, physicalId, resourceType, properties, previousProperties) {
31740
31929
  switch (resourceType) {
31741
31930
  case "AWS::ServiceDiscovery::PrivateDnsNamespace":
31742
- return this.updateNamespace(logicalId, physicalId);
31931
+ return this.updateNamespace(logicalId, physicalId, resourceType, properties);
31743
31932
  case "AWS::ServiceDiscovery::Service":
31744
- return this.updateService(logicalId, physicalId);
31933
+ return this.updateService(
31934
+ logicalId,
31935
+ physicalId,
31936
+ resourceType,
31937
+ properties,
31938
+ previousProperties
31939
+ );
31745
31940
  default:
31746
31941
  throw new ProvisioningError(
31747
31942
  `Unsupported resource type: ${resourceType}`,
@@ -31824,14 +32019,66 @@ var ServiceDiscoveryProvider = class {
31824
32019
  );
31825
32020
  }
31826
32021
  }
31827
- updateNamespace(logicalId, _physicalId) {
31828
- return Promise.reject(
31829
- new ResourceUpdateNotSupportedError(
31830
- "AWS::ServiceDiscovery::PrivateDnsNamespace",
32022
+ /**
32023
+ * Update a private DNS namespace.
32024
+ *
32025
+ * AWS exposes `UpdatePrivateDnsNamespace` for two mutable surfaces:
32026
+ * - `Description`
32027
+ * - `Properties.DnsProperties.SOA.TTL`
32028
+ *
32029
+ * `Name` and `Vpc` are immutable; the deploy engine's
32030
+ * replacement-detection layer routes those through DELETE+CREATE
32031
+ * before this method is ever called, so we do not validate them here.
32032
+ *
32033
+ * Empty-string Description is intentionally allowed through (`!== undefined`
32034
+ * gate, not truthy) so `cdkd drift --revert` can clear a console-side ADD.
32035
+ */
32036
+ async updateNamespace(logicalId, physicalId, resourceType, properties) {
32037
+ this.logger.debug(`Updating private DNS namespace ${logicalId}: ${physicalId}`);
32038
+ const client = this.getClient();
32039
+ const namespaceChange = {};
32040
+ if (properties["Description"] !== void 0) {
32041
+ namespaceChange.Description = properties["Description"];
32042
+ }
32043
+ const propsBag = properties["Properties"];
32044
+ const dnsProps = propsBag?.["DnsProperties"];
32045
+ const soa = dnsProps?.["SOA"];
32046
+ if (soa?.TTL !== void 0) {
32047
+ namespaceChange.Properties = {
32048
+ DnsProperties: {
32049
+ SOA: { TTL: Number(soa.TTL) }
32050
+ }
32051
+ };
32052
+ }
32053
+ if (Object.keys(namespaceChange).length === 0) {
32054
+ this.logger.debug(`No mutable diff for PrivateDnsNamespace ${logicalId}, skipping update`);
32055
+ return { physicalId, wasReplaced: false };
32056
+ }
32057
+ try {
32058
+ const response = await client.send(
32059
+ new UpdatePrivateDnsNamespaceCommand({
32060
+ Id: physicalId,
32061
+ Namespace: namespaceChange
32062
+ })
32063
+ );
32064
+ const operationId = response.OperationId;
32065
+ if (operationId) {
32066
+ await this.pollOperation(operationId, logicalId, resourceType);
32067
+ }
32068
+ this.logger.debug(`Successfully updated private DNS namespace ${logicalId}`);
32069
+ return { physicalId, wasReplaced: false };
32070
+ } catch (error) {
32071
+ if (error instanceof ProvisioningError)
32072
+ throw error;
32073
+ const cause = error instanceof Error ? error : void 0;
32074
+ throw new ProvisioningError(
32075
+ `Failed to update private DNS namespace ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
32076
+ resourceType,
31831
32077
  logicalId,
31832
- "PrivateDnsNamespace updates are not yet implemented in cdkd; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
31833
- )
31834
- );
32078
+ physicalId,
32079
+ cause
32080
+ );
32081
+ }
31835
32082
  }
31836
32083
  async deleteNamespace(logicalId, physicalId, resourceType, context) {
31837
32084
  this.logger.debug(`Deleting private DNS namespace ${logicalId}: ${physicalId}`);
@@ -31926,14 +32173,69 @@ var ServiceDiscoveryProvider = class {
31926
32173
  );
31927
32174
  }
31928
32175
  }
31929
- updateService(logicalId, _physicalId) {
31930
- return Promise.reject(
31931
- new ResourceUpdateNotSupportedError(
31932
- "AWS::ServiceDiscovery::Service",
32176
+ /**
32177
+ * Update a service discovery service.
32178
+ *
32179
+ * Per AWS docs, `UpdateService` accepts a `ServiceChange` body with
32180
+ * `Description`, `DnsConfig.DnsRecords` (TTLs etc. — `NamespaceId` /
32181
+ * `RoutingPolicy` are not part of the change shape and are immutable
32182
+ * here), and `HealthCheckConfig`. `Name` / `NamespaceId` /
32183
+ * `HealthCheckCustomConfig` are immutable on UpdateService — the
32184
+ * replacement-detection layer routes those through DELETE+CREATE.
32185
+ *
32186
+ * Per AWS docs, omitting `DnsRecords` / `HealthCheckConfig` from the
32187
+ * request DELETES that configuration. To preserve fields cdkd is not
32188
+ * actively reverting, we always echo the AWS-current value when the
32189
+ * caller did not supply a change. `cdkd drift --revert` passes the
32190
+ * full AWS-current snapshot as `properties`, so the round-trip is
32191
+ * value-preserving.
32192
+ */
32193
+ async updateService(logicalId, physicalId, resourceType, properties, _previousProperties) {
32194
+ this.logger.debug(`Updating service discovery service ${logicalId}: ${physicalId}`);
32195
+ const client = this.getClient();
32196
+ const serviceChange = {};
32197
+ if (properties["Description"] !== void 0) {
32198
+ serviceChange.Description = properties["Description"];
32199
+ }
32200
+ const dnsConfig = properties["DnsConfig"];
32201
+ if (dnsConfig?.DnsRecords !== void 0) {
32202
+ const change = { DnsRecords: dnsConfig.DnsRecords };
32203
+ serviceChange.DnsConfig = change;
32204
+ }
32205
+ if (properties["HealthCheckConfig"] !== void 0) {
32206
+ serviceChange.HealthCheckConfig = properties["HealthCheckConfig"];
32207
+ }
32208
+ if (Object.keys(serviceChange).length === 0) {
32209
+ this.logger.debug(
32210
+ `No mutable diff for ServiceDiscovery Service ${logicalId}, skipping update`
32211
+ );
32212
+ return { physicalId, wasReplaced: false };
32213
+ }
32214
+ try {
32215
+ const response = await client.send(
32216
+ new UpdateServiceCommand2({
32217
+ Id: physicalId,
32218
+ Service: serviceChange
32219
+ })
32220
+ );
32221
+ const operationId = response.OperationId;
32222
+ if (operationId) {
32223
+ await this.pollOperation(operationId, logicalId, resourceType);
32224
+ }
32225
+ this.logger.debug(`Successfully updated service discovery service ${logicalId}`);
32226
+ return { physicalId, wasReplaced: false };
32227
+ } catch (error) {
32228
+ if (error instanceof ProvisioningError)
32229
+ throw error;
32230
+ const cause = error instanceof Error ? error : void 0;
32231
+ throw new ProvisioningError(
32232
+ `Failed to update service discovery service ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
32233
+ resourceType,
31933
32234
  logicalId,
31934
- "ServiceDiscovery Service updates are not yet implemented in cdkd; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
31935
- )
31936
- );
32235
+ physicalId,
32236
+ cause
32237
+ );
32238
+ }
31937
32239
  }
31938
32240
  async deleteService(logicalId, physicalId, resourceType, context) {
31939
32241
  this.logger.debug(`Deleting service discovery service ${logicalId}: ${physicalId}`);
@@ -33020,6 +33322,7 @@ var AppSyncProvider = class {
33020
33322
  import {
33021
33323
  GlueClient,
33022
33324
  CreateDatabaseCommand,
33325
+ UpdateDatabaseCommand,
33023
33326
  DeleteDatabaseCommand,
33024
33327
  CreateTableCommand as CreateTableCommand2,
33025
33328
  UpdateTableCommand,
@@ -33066,11 +33369,7 @@ var GlueProvider = class {
33066
33369
  async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
33067
33370
  switch (resourceType) {
33068
33371
  case "AWS::Glue::Database":
33069
- throw new ResourceUpdateNotSupportedError(
33070
- resourceType,
33071
- logicalId,
33072
- "Glue Database updates are not yet implemented in cdkd; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
33073
- );
33372
+ return this.updateDatabase(logicalId, physicalId, resourceType, properties);
33074
33373
  case "AWS::Glue::Table":
33075
33374
  return this.updateTable(logicalId, physicalId, resourceType, properties);
33076
33375
  default:
@@ -33121,12 +33420,7 @@ var GlueProvider = class {
33121
33420
  await this.getClient().send(
33122
33421
  new CreateDatabaseCommand({
33123
33422
  CatalogId: catalogId,
33124
- DatabaseInput: {
33125
- Name: databaseName,
33126
- Description: databaseInput["Description"],
33127
- LocationUri: databaseInput["LocationUri"],
33128
- Parameters: databaseInput["Parameters"]
33129
- }
33423
+ DatabaseInput: this.buildDatabaseInput(databaseInput, databaseName)
33130
33424
  })
33131
33425
  );
33132
33426
  this.logger.debug(`Successfully created Glue Database ${logicalId}: ${databaseName}`);
@@ -33145,6 +33439,42 @@ var GlueProvider = class {
33145
33439
  );
33146
33440
  }
33147
33441
  }
33442
+ async updateDatabase(logicalId, physicalId, resourceType, properties) {
33443
+ this.logger.debug(`Updating Glue Database ${logicalId}: ${physicalId}`);
33444
+ const databaseInput = properties["DatabaseInput"];
33445
+ if (!databaseInput) {
33446
+ throw new ProvisioningError(
33447
+ `DatabaseInput is required for Glue Database update ${logicalId}`,
33448
+ resourceType,
33449
+ logicalId,
33450
+ physicalId
33451
+ );
33452
+ }
33453
+ const catalogId = properties["CatalogId"];
33454
+ try {
33455
+ await this.getClient().send(
33456
+ new UpdateDatabaseCommand({
33457
+ ...catalogId !== void 0 && { CatalogId: catalogId },
33458
+ Name: physicalId,
33459
+ DatabaseInput: this.buildDatabaseInput(databaseInput, physicalId)
33460
+ })
33461
+ );
33462
+ this.logger.debug(`Successfully updated Glue Database ${logicalId}`);
33463
+ return {
33464
+ physicalId,
33465
+ wasReplaced: false
33466
+ };
33467
+ } catch (error) {
33468
+ const cause = error instanceof Error ? error : void 0;
33469
+ throw new ProvisioningError(
33470
+ `Failed to update Glue Database ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
33471
+ resourceType,
33472
+ logicalId,
33473
+ physicalId,
33474
+ cause
33475
+ );
33476
+ }
33477
+ }
33148
33478
  async deleteDatabase(logicalId, physicalId, resourceType, properties, context) {
33149
33479
  this.logger.debug(`Deleting Glue Database ${logicalId}: ${physicalId}`);
33150
33480
  try {
@@ -33316,6 +33646,31 @@ var GlueProvider = class {
33316
33646
  }
33317
33647
  }
33318
33648
  // ─── Helpers ───────────────────────────────────────────────────────
33649
+ /**
33650
+ * Build DatabaseInput for Glue API from CFn template properties.
33651
+ *
33652
+ * Used by both `createDatabase` and `updateDatabase` so the same
33653
+ * field-by-field shape is sent on both paths. Optional fields use
33654
+ * `!== undefined` gates (per `feedback_update_optional_field_undefined_check.md`)
33655
+ * so empty-string Description, empty Parameters map, etc. reach AWS
33656
+ * intact — `cdkd drift --revert` relies on this to clear console-side
33657
+ * additions.
33658
+ */
33659
+ buildDatabaseInput(databaseInput, fallbackName) {
33660
+ const result = {
33661
+ Name: databaseInput["Name"] ?? fallbackName
33662
+ };
33663
+ if (databaseInput["Description"] !== void 0) {
33664
+ result.Description = databaseInput["Description"];
33665
+ }
33666
+ if (databaseInput["LocationUri"] !== void 0) {
33667
+ result.LocationUri = databaseInput["LocationUri"];
33668
+ }
33669
+ if (databaseInput["Parameters"] !== void 0) {
33670
+ result.Parameters = databaseInput["Parameters"];
33671
+ }
33672
+ return result;
33673
+ }
33319
33674
  /**
33320
33675
  * Build TableInput for Glue API from CFn template properties
33321
33676
  */
@@ -34781,10 +35136,12 @@ var KinesisStreamProvider = class {
34781
35136
  import {
34782
35137
  EFSClient,
34783
35138
  CreateFileSystemCommand,
35139
+ UpdateFileSystemCommand,
34784
35140
  DeleteFileSystemCommand,
34785
35141
  CreateMountTargetCommand,
34786
35142
  DeleteMountTargetCommand,
34787
35143
  DescribeMountTargetsCommand,
35144
+ ModifyMountTargetSecurityGroupsCommand,
34788
35145
  CreateAccessPointCommand,
34789
35146
  DeleteAccessPointCommand,
34790
35147
  DescribeFileSystemsCommand,
@@ -34842,29 +35199,122 @@ var EFSProvider = class {
34842
35199
  }
34843
35200
  }
34844
35201
  /**
34845
- * EFS resources are treated as immutable by cdkd's `update()`. The deploy
34846
- * engine recreates them on property changes via immutable-property
34847
- * detection. (AWS does expose `UpdateFileSystem` for ThroughputMode and
34848
- * `ModifyMountTargetSecurityGroups` for mount-target SGs those are
34849
- * deferred to a follow-up PR.) `cdkd drift --revert` surfaces a clear
34850
- * "use --replace or re-deploy" message instead of silently no-op'ing.
35202
+ * Mutable surfaces by resource type:
35203
+ * - `AWS::EFS::FileSystem` `UpdateFileSystem` (ThroughputMode,
35204
+ * ProvisionedThroughputInMibps). Other property changes
35205
+ * (Encrypted / KmsKeyId / PerformanceMode / etc.) are routed
35206
+ * through DELETE+CREATE by the replacement-detection layer; if a
35207
+ * diff somehow includes them, defensively reject.
35208
+ * - `AWS::EFS::MountTarget` → `ModifyMountTargetSecurityGroups`
35209
+ * (SecurityGroups only). IpAddress / SubnetId / FileSystemId are
35210
+ * immutable.
35211
+ * - `AWS::EFS::AccessPoint` → no mutable surface; AWS recreates on
35212
+ * every change. Reject so `cdkd drift --revert` surfaces a clear
35213
+ * "use --replace" hint.
34851
35214
  */
34852
- update(logicalId, physicalId, resourceType, _properties, _previousProperties) {
34853
- if (resourceType !== "AWS::EFS::FileSystem" && resourceType !== "AWS::EFS::MountTarget" && resourceType !== "AWS::EFS::AccessPoint") {
35215
+ update(logicalId, physicalId, resourceType, properties, previousProperties) {
35216
+ switch (resourceType) {
35217
+ case "AWS::EFS::FileSystem":
35218
+ return this.updateFileSystem(
35219
+ logicalId,
35220
+ physicalId,
35221
+ resourceType,
35222
+ properties,
35223
+ previousProperties
35224
+ );
35225
+ case "AWS::EFS::MountTarget":
35226
+ return this.updateMountTarget(logicalId, physicalId, resourceType, properties);
35227
+ case "AWS::EFS::AccessPoint":
35228
+ return Promise.reject(
35229
+ new ResourceUpdateNotSupportedError(
35230
+ resourceType,
35231
+ logicalId,
35232
+ "EFS AccessPoint is recreated on property changes; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
35233
+ )
35234
+ );
35235
+ default:
35236
+ throw new ProvisioningError(
35237
+ `Unsupported resource type: ${resourceType}`,
35238
+ resourceType,
35239
+ logicalId,
35240
+ physicalId
35241
+ );
35242
+ }
35243
+ }
35244
+ async updateFileSystem(logicalId, physicalId, resourceType, properties, previousProperties) {
35245
+ const immutableKeys = ["Encrypted", "KmsKeyId", "PerformanceMode"];
35246
+ for (const key of immutableKeys) {
35247
+ const next = properties[key];
35248
+ const prev = previousProperties[key];
35249
+ if (next !== void 0 && prev !== void 0 && JSON.stringify(next) !== JSON.stringify(prev)) {
35250
+ throw new ResourceUpdateNotSupportedError(
35251
+ resourceType,
35252
+ logicalId,
35253
+ `EFS FileSystem ${key} is immutable; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack`
35254
+ );
35255
+ }
35256
+ }
35257
+ const newThroughputMode = properties["ThroughputMode"];
35258
+ const newProvisioned = properties["ProvisionedThroughputInMibps"];
35259
+ const oldThroughputMode = previousProperties["ThroughputMode"];
35260
+ const oldProvisioned = previousProperties["ProvisionedThroughputInMibps"];
35261
+ const throughputModeChanged = newThroughputMode !== void 0 && newThroughputMode !== oldThroughputMode;
35262
+ const provisionedChanged = newProvisioned !== void 0 && newProvisioned !== oldProvisioned;
35263
+ if (!throughputModeChanged && !provisionedChanged) {
35264
+ this.logger.debug(`No mutable diff for EFS FileSystem ${logicalId}, skipping update`);
35265
+ return { physicalId, wasReplaced: false };
35266
+ }
35267
+ this.logger.debug(`Updating EFS FileSystem ${logicalId}: ${physicalId}`);
35268
+ try {
35269
+ await this.getClient().send(
35270
+ new UpdateFileSystemCommand({
35271
+ FileSystemId: physicalId,
35272
+ ...throughputModeChanged && { ThroughputMode: newThroughputMode },
35273
+ ...provisionedChanged && { ProvisionedThroughputInMibps: newProvisioned }
35274
+ })
35275
+ );
35276
+ await this.waitForFileSystemAvailable(physicalId, logicalId, resourceType);
35277
+ this.logger.debug(`Successfully updated EFS FileSystem ${logicalId}`);
35278
+ return { physicalId, wasReplaced: false };
35279
+ } catch (error) {
35280
+ if (error instanceof ProvisioningError)
35281
+ throw error;
35282
+ const cause = error instanceof Error ? error : void 0;
34854
35283
  throw new ProvisioningError(
34855
- `Unsupported resource type: ${resourceType}`,
35284
+ `Failed to update EFS FileSystem ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
34856
35285
  resourceType,
34857
35286
  logicalId,
34858
- physicalId
35287
+ physicalId,
35288
+ cause
34859
35289
  );
34860
35290
  }
34861
- return Promise.reject(
34862
- new ResourceUpdateNotSupportedError(
35291
+ }
35292
+ async updateMountTarget(logicalId, physicalId, resourceType, properties) {
35293
+ this.logger.debug(`Updating EFS MountTarget ${logicalId}: ${physicalId}`);
35294
+ const securityGroups = properties["SecurityGroups"];
35295
+ if (securityGroups === void 0) {
35296
+ this.logger.debug(`No mutable diff for EFS MountTarget ${logicalId}, skipping update`);
35297
+ return { physicalId, wasReplaced: false };
35298
+ }
35299
+ try {
35300
+ await this.getClient().send(
35301
+ new ModifyMountTargetSecurityGroupsCommand({
35302
+ MountTargetId: physicalId,
35303
+ SecurityGroups: securityGroups
35304
+ })
35305
+ );
35306
+ this.logger.debug(`Successfully updated EFS MountTarget ${logicalId}`);
35307
+ return { physicalId, wasReplaced: false };
35308
+ } catch (error) {
35309
+ const cause = error instanceof Error ? error : void 0;
35310
+ throw new ProvisioningError(
35311
+ `Failed to update EFS MountTarget ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
34863
35312
  resourceType,
34864
35313
  logicalId,
34865
- "EFS resources are recreated on property changes; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
34866
- )
34867
- );
35314
+ physicalId,
35315
+ cause
35316
+ );
35317
+ }
34868
35318
  }
34869
35319
  async delete(logicalId, physicalId, resourceType, _properties, context) {
34870
35320
  switch (resourceType) {
@@ -45339,7 +45789,7 @@ function reorderArgs(argv) {
45339
45789
  }
45340
45790
  async function main() {
45341
45791
  const program = new Command14();
45342
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.52.0");
45792
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.54.0");
45343
45793
  program.addCommand(createBootstrapCommand());
45344
45794
  program.addCommand(createSynthCommand());
45345
45795
  program.addCommand(createListCommand());