@go-to-k/cdkd 0.43.0 → 0.45.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
@@ -1187,6 +1187,22 @@ var PartialFailureError = class _PartialFailureError extends CdkdError {
1187
1187
  Object.setPrototypeOf(this, _PartialFailureError.prototype);
1188
1188
  }
1189
1189
  };
1190
+ var ResourceUpdateNotSupportedError = class _ResourceUpdateNotSupportedError extends CdkdError {
1191
+ constructor(resourceType, logicalId, suggestion, cause) {
1192
+ const tail = suggestion ? suggestion : "use cdkd deploy with --replace, or change the resource definition to create a new version";
1193
+ super(
1194
+ `${resourceType} (${logicalId}) cannot be updated in place: ${tail}.`,
1195
+ "RESOURCE_UPDATE_NOT_SUPPORTED",
1196
+ cause
1197
+ );
1198
+ this.resourceType = resourceType;
1199
+ this.logicalId = logicalId;
1200
+ this.suggestion = suggestion;
1201
+ this.name = "ResourceUpdateNotSupportedError";
1202
+ Object.setPrototypeOf(this, _ResourceUpdateNotSupportedError.prototype);
1203
+ }
1204
+ exitCode = 2;
1205
+ };
1190
1206
  function isCdkdError(error) {
1191
1207
  return error instanceof CdkdError;
1192
1208
  }
@@ -21335,20 +21351,22 @@ var ApiGatewayProvider = class _ApiGatewayProvider {
21335
21351
  }
21336
21352
  }
21337
21353
  /**
21338
- * Update an API Gateway Authorizer
21354
+ * Update an API Gateway Authorizer.
21339
21355
  *
21340
- * Authorizer updates are not commonly needed. For now, this is a no-op.
21341
- * The deployment engine handles replacement for immutable property changes.
21356
+ * AWS exposes `UpdateAuthorizer` (PATCH) but cdkd does not yet plumb the
21357
+ * patch-operations builder through. Authorizers are recreated by the
21358
+ * deploy engine's immutable-property replacement path. `cdkd drift
21359
+ * --revert` surfaces a clear "use --replace or re-deploy" message
21360
+ * instead of silently no-op'ing the revert.
21342
21361
  */
21343
- updateAuthorizer(logicalId, physicalId, _resourceType) {
21344
- this.logger.debug(`Updating API Gateway Authorizer ${logicalId}: ${physicalId} (no-op)`);
21345
- return Promise.resolve({
21346
- physicalId,
21347
- wasReplaced: false,
21348
- attributes: {
21349
- AuthorizerId: physicalId
21350
- }
21351
- });
21362
+ updateAuthorizer(logicalId, _physicalId, _resourceType) {
21363
+ return Promise.reject(
21364
+ new ResourceUpdateNotSupportedError(
21365
+ "AWS::ApiGateway::Authorizer",
21366
+ logicalId,
21367
+ "API Gateway Authorizer updates are not yet implemented in cdkd; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
21368
+ )
21369
+ );
21352
21370
  }
21353
21371
  /**
21354
21372
  * Delete an API Gateway Authorizer
@@ -21580,20 +21598,20 @@ var ApiGatewayProvider = class _ApiGatewayProvider {
21580
21598
  }
21581
21599
  }
21582
21600
  /**
21583
- * Update an API Gateway Deployment
21601
+ * Update an API Gateway Deployment.
21584
21602
  *
21585
- * Deployments are immutable - updates are not supported.
21586
- * The deployment engine should handle replacement if needed.
21603
+ * Deployments are immutable every property change requires a fresh
21604
+ * Deployment. `cdkd drift --revert` therefore throws
21605
+ * `ResourceUpdateNotSupportedError` instead of silently no-op'ing.
21587
21606
  */
21588
- updateDeployment(logicalId, physicalId, _resourceType) {
21589
- this.logger.debug(`Updating API Gateway Deployment ${logicalId}: ${physicalId} (no-op)`);
21590
- return Promise.resolve({
21591
- physicalId,
21592
- wasReplaced: false,
21593
- attributes: {
21594
- DeploymentId: physicalId
21595
- }
21596
- });
21607
+ updateDeployment(logicalId, _physicalId, _resourceType) {
21608
+ return Promise.reject(
21609
+ new ResourceUpdateNotSupportedError(
21610
+ "AWS::ApiGateway::Deployment",
21611
+ logicalId,
21612
+ "API Gateway Deployment is immutable; re-deploy with cdkd deploy --replace, or change the resource definition to create a new Deployment"
21613
+ )
21614
+ );
21597
21615
  }
21598
21616
  /**
21599
21617
  * Delete an API Gateway Deployment
@@ -21894,17 +21912,22 @@ var ApiGatewayProvider = class _ApiGatewayProvider {
21894
21912
  }
21895
21913
  }
21896
21914
  /**
21897
- * Update an API Gateway Method
21915
+ * Update an API Gateway Method.
21898
21916
  *
21899
- * Methods are typically replaced via new deployment, so this is a no-op.
21917
+ * AWS exposes `UpdateMethod` (PATCH) but cdkd does not yet plumb the
21918
+ * patch-operations builder through. Methods are recreated by the deploy
21919
+ * engine's immutable-property replacement path. `cdkd drift --revert`
21920
+ * surfaces a clear "use --replace or re-deploy" message instead of
21921
+ * silently no-op'ing the revert.
21900
21922
  */
21901
- updateMethod(logicalId, physicalId) {
21902
- this.logger.debug(`Updating API Gateway Method ${logicalId}: ${physicalId} (no-op)`);
21903
- return Promise.resolve({
21904
- physicalId,
21905
- wasReplaced: false,
21906
- attributes: {}
21907
- });
21923
+ updateMethod(logicalId, _physicalId) {
21924
+ return Promise.reject(
21925
+ new ResourceUpdateNotSupportedError(
21926
+ "AWS::ApiGateway::Method",
21927
+ logicalId,
21928
+ "API Gateway Method updates are not yet implemented in cdkd; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
21929
+ )
21930
+ );
21908
21931
  }
21909
21932
  /**
21910
21933
  * Delete an API Gateway Method
@@ -22282,13 +22305,23 @@ var ApiGatewayV2Provider = class {
22282
22305
  );
22283
22306
  }
22284
22307
  }
22285
- update(logicalId, physicalId, resourceType, _properties, _previousProperties) {
22286
- this.logger.debug(`Updating ${resourceType} ${logicalId}: ${physicalId} (no-op)`);
22287
- return Promise.resolve({
22288
- physicalId,
22289
- wasReplaced: false,
22290
- attributes: {}
22291
- });
22308
+ /**
22309
+ * HTTP API resources are treated as immutable by cdkd: the deploy engine
22310
+ * recreates them on property changes via the immutable-property
22311
+ * replacement path. AWS does expose `UpdateApi` / `UpdateRoute` /
22312
+ * `UpdateIntegration` / `UpdateStage` / `UpdateAuthorizer`, but cdkd
22313
+ * does not yet plumb them through. `cdkd drift --revert` surfaces a
22314
+ * clear "use --replace or re-deploy" message instead of silently
22315
+ * no-op'ing the revert.
22316
+ */
22317
+ update(logicalId, _physicalId, resourceType, _properties, _previousProperties) {
22318
+ return Promise.reject(
22319
+ new ResourceUpdateNotSupportedError(
22320
+ resourceType,
22321
+ logicalId,
22322
+ "API Gateway V2 (HTTP API) resources are recreated on property changes; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
22323
+ )
22324
+ );
22292
22325
  }
22293
22326
  async delete(logicalId, physicalId, resourceType, properties, context) {
22294
22327
  switch (resourceType) {
@@ -22964,6 +22997,7 @@ import {
22964
22997
  CreateCloudFrontOriginAccessIdentityCommand,
22965
22998
  DeleteCloudFrontOriginAccessIdentityCommand,
22966
22999
  GetCloudFrontOriginAccessIdentityCommand as GetCloudFrontOriginAccessIdentityCommand2,
23000
+ UpdateCloudFrontOriginAccessIdentityCommand,
22967
23001
  NoSuchCloudFrontOriginAccessIdentity
22968
23002
  } from "@aws-sdk/client-cloudfront";
22969
23003
  init_aws_clients();
@@ -23019,17 +23053,61 @@ var CloudFrontOAIProvider = class {
23019
23053
  }
23020
23054
  }
23021
23055
  /**
23022
- * Update a CloudFront Origin Access Identity
23056
+ * Update a CloudFront Origin Access Identity.
23057
+ *
23058
+ * Only the `Comment` field is mutable on an OAI; `CallerReference` is set
23059
+ * by cdkd at create time and cannot be changed. AWS exposes a single
23060
+ * `UpdateCloudFrontOriginAccessIdentity` call that requires the current
23061
+ * `ETag` (fetched via `GetCloudFrontOriginAccessIdentity`) and overwrites
23062
+ * the entire `CloudFrontOriginAccessIdentityConfig`.
23023
23063
  *
23024
- * OAI config is effectively immutable (only Comment can change, which is cosmetic).
23025
- * No replacement needed for Comment changes.
23064
+ * Used by `cdkd drift --revert` to push the cdkd-state Comment back into
23065
+ * AWS; on the normal deploy path this is also exercised when a user
23066
+ * tweaks the Comment in their CDK code.
23026
23067
  */
23027
- update(logicalId, physicalId, _resourceType, _properties, _previousProperties) {
23028
- this.logger.debug(`Update requested for CloudFront OAI ${logicalId}: ${physicalId} (no-op)`);
23029
- return Promise.resolve({
23030
- physicalId,
23031
- wasReplaced: false
23032
- });
23068
+ async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
23069
+ this.logger.debug(`Updating CloudFront OAI ${logicalId}: ${physicalId}`);
23070
+ const config = properties["CloudFrontOriginAccessIdentityConfig"];
23071
+ const comment = config?.["Comment"] ?? "";
23072
+ try {
23073
+ const getResponse = await this.cloudFrontClient.send(
23074
+ new GetCloudFrontOriginAccessIdentityCommand2({ Id: physicalId })
23075
+ );
23076
+ const etag = getResponse.ETag;
23077
+ if (!etag) {
23078
+ throw new Error("GetCloudFrontOriginAccessIdentity did not return ETag");
23079
+ }
23080
+ await this.cloudFrontClient.send(
23081
+ new UpdateCloudFrontOriginAccessIdentityCommand({
23082
+ Id: physicalId,
23083
+ IfMatch: etag,
23084
+ CloudFrontOriginAccessIdentityConfig: {
23085
+ // CallerReference is immutable; preserve whatever the OAI was
23086
+ // created with so AWS does not reject the update.
23087
+ CallerReference: getResponse.CloudFrontOriginAccessIdentity?.CloudFrontOriginAccessIdentityConfig?.CallerReference ?? logicalId,
23088
+ Comment: comment
23089
+ }
23090
+ })
23091
+ );
23092
+ this.logger.debug(`Successfully updated CloudFront OAI ${logicalId}`);
23093
+ return {
23094
+ physicalId,
23095
+ wasReplaced: false,
23096
+ attributes: {
23097
+ Id: physicalId,
23098
+ S3CanonicalUserId: getResponse.CloudFrontOriginAccessIdentity?.S3CanonicalUserId
23099
+ }
23100
+ };
23101
+ } catch (error) {
23102
+ const cause = error instanceof Error ? error : void 0;
23103
+ throw new ProvisioningError(
23104
+ `Failed to update CloudFront OAI ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
23105
+ resourceType,
23106
+ logicalId,
23107
+ physicalId,
23108
+ cause
23109
+ );
23110
+ }
23033
23111
  }
23034
23112
  /**
23035
23113
  * Delete a CloudFront Origin Access Identity
@@ -25570,34 +25648,14 @@ var ELBv2Provider = class {
25570
25648
  );
25571
25649
  }
25572
25650
  }
25573
- async updateLoadBalancer(logicalId, physicalId, resourceType, _properties) {
25574
- this.logger.debug(`Updating LoadBalancer ${logicalId}: ${physicalId}`);
25575
- try {
25576
- const describeResponse = await this.getClient().send(
25577
- new DescribeLoadBalancersCommand2({ LoadBalancerArns: [physicalId] })
25578
- );
25579
- const lb = describeResponse.LoadBalancers?.[0];
25580
- return {
25581
- physicalId,
25582
- wasReplaced: false,
25583
- attributes: {
25584
- DNSName: lb?.DNSName,
25585
- CanonicalHostedZoneID: lb?.CanonicalHostedZoneId,
25586
- LoadBalancerArn: physicalId,
25587
- LoadBalancerFullName: physicalId.split("/").slice(1).join("/"),
25588
- LoadBalancerName: lb?.LoadBalancerName
25589
- }
25590
- };
25591
- } catch (error) {
25592
- const cause = error instanceof Error ? error : void 0;
25593
- throw new ProvisioningError(
25594
- `Failed to update LoadBalancer ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
25595
- resourceType,
25651
+ updateLoadBalancer(logicalId, _physicalId, _resourceType, _properties) {
25652
+ return Promise.reject(
25653
+ new ResourceUpdateNotSupportedError(
25654
+ "AWS::ElasticLoadBalancingV2::LoadBalancer",
25596
25655
  logicalId,
25597
- physicalId,
25598
- cause
25599
- );
25600
- }
25656
+ "ELBv2 LoadBalancer in-place updates are not yet implemented in cdkd; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
25657
+ )
25658
+ );
25601
25659
  }
25602
25660
  async deleteLoadBalancer(logicalId, physicalId, resourceType, context) {
25603
25661
  this.logger.debug(`Deleting LoadBalancer ${logicalId}: ${physicalId}`);
@@ -29590,15 +29648,14 @@ var ServiceDiscoveryProvider = class {
29590
29648
  );
29591
29649
  }
29592
29650
  }
29593
- updateNamespace(logicalId, physicalId) {
29594
- this.logger.debug(`Updating private DNS namespace ${logicalId}: ${physicalId} (no-op)`);
29595
- return Promise.resolve({
29596
- physicalId,
29597
- wasReplaced: false,
29598
- attributes: {
29599
- Id: physicalId
29600
- }
29601
- });
29651
+ updateNamespace(logicalId, _physicalId) {
29652
+ return Promise.reject(
29653
+ new ResourceUpdateNotSupportedError(
29654
+ "AWS::ServiceDiscovery::PrivateDnsNamespace",
29655
+ logicalId,
29656
+ "PrivateDnsNamespace updates are not yet implemented in cdkd; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
29657
+ )
29658
+ );
29602
29659
  }
29603
29660
  async deleteNamespace(logicalId, physicalId, resourceType, context) {
29604
29661
  this.logger.debug(`Deleting private DNS namespace ${logicalId}: ${physicalId}`);
@@ -29693,15 +29750,14 @@ var ServiceDiscoveryProvider = class {
29693
29750
  );
29694
29751
  }
29695
29752
  }
29696
- updateService(logicalId, physicalId) {
29697
- this.logger.debug(`Updating service discovery service ${logicalId}: ${physicalId} (no-op)`);
29698
- return Promise.resolve({
29699
- physicalId,
29700
- wasReplaced: false,
29701
- attributes: {
29702
- Id: physicalId
29703
- }
29704
- });
29753
+ updateService(logicalId, _physicalId) {
29754
+ return Promise.reject(
29755
+ new ResourceUpdateNotSupportedError(
29756
+ "AWS::ServiceDiscovery::Service",
29757
+ logicalId,
29758
+ "ServiceDiscovery Service updates are not yet implemented in cdkd; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
29759
+ )
29760
+ );
29705
29761
  }
29706
29762
  async deleteService(logicalId, physicalId, resourceType, context) {
29707
29763
  this.logger.debug(`Deleting service discovery service ${logicalId}: ${physicalId}`);
@@ -30049,9 +30105,22 @@ var AppSyncProvider = class {
30049
30105
  );
30050
30106
  }
30051
30107
  }
30052
- update(logicalId, physicalId, resourceType, _properties, _previousProperties) {
30053
- this.logger.debug(`Update for ${resourceType} ${logicalId} (${physicalId}) - no-op, immutable`);
30054
- return Promise.resolve({ physicalId, wasReplaced: false });
30108
+ /**
30109
+ * AppSync resources are treated as immutable by cdkd: every supported
30110
+ * type (`GraphQLApi`, `GraphQLSchema`, `DataSource`, `Resolver`,
30111
+ * `ApiKey`) is recreated on property changes via the deploy engine's
30112
+ * immutable-property replacement path. There is no in-place update,
30113
+ * so `cdkd drift --revert` surfaces a clear "use --replace or
30114
+ * re-deploy" message instead of silently no-op'ing the revert.
30115
+ */
30116
+ update(logicalId, _physicalId, resourceType, _properties, _previousProperties) {
30117
+ return Promise.reject(
30118
+ new ResourceUpdateNotSupportedError(
30119
+ resourceType,
30120
+ logicalId,
30121
+ "AppSync resources are recreated on property changes; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
30122
+ )
30123
+ );
30055
30124
  }
30056
30125
  async delete(logicalId, physicalId, resourceType, _properties, context) {
30057
30126
  switch (resourceType) {
@@ -30788,10 +30857,11 @@ var GlueProvider = class {
30788
30857
  async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
30789
30858
  switch (resourceType) {
30790
30859
  case "AWS::Glue::Database":
30791
- this.logger.debug(
30792
- `Update for ${resourceType} ${logicalId} (${physicalId}) - no-op, immutable`
30860
+ throw new ResourceUpdateNotSupportedError(
30861
+ resourceType,
30862
+ logicalId,
30863
+ "Glue Database updates are not yet implemented in cdkd; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
30793
30864
  );
30794
- return { physicalId, wasReplaced: false };
30795
30865
  case "AWS::Glue::Table":
30796
30866
  return this.updateTable(logicalId, physicalId, resourceType, properties);
30797
30867
  default:
@@ -32414,8 +32484,15 @@ var EFSProvider = class {
32414
32484
  );
32415
32485
  }
32416
32486
  }
32487
+ /**
32488
+ * EFS resources are treated as immutable by cdkd's `update()`. The deploy
32489
+ * engine recreates them on property changes via immutable-property
32490
+ * detection. (AWS does expose `UpdateFileSystem` for ThroughputMode and
32491
+ * `ModifyMountTargetSecurityGroups` for mount-target SGs — those are
32492
+ * deferred to a follow-up PR.) `cdkd drift --revert` surfaces a clear
32493
+ * "use --replace or re-deploy" message instead of silently no-op'ing.
32494
+ */
32417
32495
  update(logicalId, physicalId, resourceType, _properties, _previousProperties) {
32418
- this.logger.debug(`Update for ${resourceType} ${logicalId} (${physicalId}) - no-op, immutable`);
32419
32496
  if (resourceType !== "AWS::EFS::FileSystem" && resourceType !== "AWS::EFS::MountTarget" && resourceType !== "AWS::EFS::AccessPoint") {
32420
32497
  throw new ProvisioningError(
32421
32498
  `Unsupported resource type: ${resourceType}`,
@@ -32424,7 +32501,13 @@ var EFSProvider = class {
32424
32501
  physicalId
32425
32502
  );
32426
32503
  }
32427
- return Promise.resolve({ physicalId, wasReplaced: false });
32504
+ return Promise.reject(
32505
+ new ResourceUpdateNotSupportedError(
32506
+ resourceType,
32507
+ logicalId,
32508
+ "EFS resources are recreated on property changes; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
32509
+ )
32510
+ );
32428
32511
  }
32429
32512
  async delete(logicalId, physicalId, resourceType, _properties, context) {
32430
32513
  switch (resourceType) {
@@ -33241,15 +33324,21 @@ var FirehoseProvider = class {
33241
33324
  }
33242
33325
  }
33243
33326
  /**
33244
- * Update a Firehose delivery stream
33245
- *
33246
- * Most changes require replacement, so this is a no-op.
33327
+ * Firehose delivery streams are treated as immutable by cdkd. Most
33328
+ * destination-config changes require replacement, and AWS's
33329
+ * `UpdateDestination` API surface is deep enough that the deploy engine's
33330
+ * immutable-property replacement path covers the common cases more
33331
+ * reliably. `cdkd drift --revert` therefore surfaces a clear "use
33332
+ * --replace or re-deploy" message instead of silently no-op'ing.
33247
33333
  */
33248
- update(logicalId, physicalId, resourceType, _properties, _previousProperties) {
33249
- this.logger.debug(
33250
- `Update for ${resourceType} ${logicalId} (${physicalId}) - no-op, most changes require replacement`
33334
+ update(logicalId, _physicalId, resourceType, _properties, _previousProperties) {
33335
+ return Promise.reject(
33336
+ new ResourceUpdateNotSupportedError(
33337
+ resourceType,
33338
+ logicalId,
33339
+ "Firehose delivery streams are recreated on property changes; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
33340
+ )
33251
33341
  );
33252
- return Promise.resolve({ physicalId, wasReplaced: false });
33253
33342
  }
33254
33343
  /**
33255
33344
  * Delete a Firehose delivery stream
@@ -38050,6 +38139,128 @@ function isPlainObject(value) {
38050
38139
  return typeof value === "object" && value !== null;
38051
38140
  }
38052
38141
 
38142
+ // src/analyzer/drift-cc-api-deny-list.ts
38143
+ var CC_API_FALLBACK_DENY_LIST = {
38144
+ // AWS::IAM::ManagedPolicy: PolicyDocument round-trips through CC API
38145
+ // URL-encoded; cdkd state stores it as a parsed JSON object. Without
38146
+ // a per-type decoder, every comparison sees a string-vs-object
38147
+ // mismatch and fires drift on every run. The IAM Role provider's
38148
+ // first-class readCurrentState handles its inline AssumeRolePolicy
38149
+ // the same way (URL-decode + JSON-parse); the same fix pattern is
38150
+ // needed for ManagedPolicy when an SDK provider is added.
38151
+ "AWS::IAM::ManagedPolicy": "PolicyDocument is URL-encoded JSON in CC API responses, but cdkd state stores it as a parsed object \u2014 needs per-type decode",
38152
+ // AWS::ApiGateway::RestApi: the `Body` property (OpenAPI spec object
38153
+ // when supplied via `Body` rather than `BodyS3Location`) is write-only
38154
+ // — `GetRestApi` does NOT return it, but cdkd state preserves the
38155
+ // object the user passed in. CC API GetResource inherits this — the
38156
+ // returned shape omits `Body`, so every drift run flags it as
38157
+ // missing-on-AWS. Comparison silently dropping it would also be
38158
+ // wrong; the right move is a dedicated SDK provider that knows to
38159
+ // skip `Body` entirely.
38160
+ "AWS::ApiGateway::RestApi": "Body / BodyS3Location are write-only inputs not returned by CC API GetResource; cdkd state preserves them",
38161
+ // AWS::CloudFormation::Stack: nested stacks aren't supported by cdkd's
38162
+ // provider registry at all; the deploy / destroy paths reject them.
38163
+ // Listing here is defense-in-depth — if a user manually crafts state
38164
+ // with one, drift via CC API would compare CFn-template-input
38165
+ // properties against CC API's stack-output shape (CC API's
38166
+ // `AWS::CloudFormation::Stack` reports outputs / status, not the
38167
+ // template parameters cdkd would have stored).
38168
+ "AWS::CloudFormation::Stack": "CC API returns runtime stack state (outputs/status), not the template parameters cdkd state stores",
38169
+ // AWS::EC2::LaunchTemplate: `LaunchTemplateData` ships with deeply
38170
+ // structured sub-objects that CC API normalizes into a versioned shape
38171
+ // — every UpdateLaunchTemplate (and even GetLaunchTemplate) bumps the
38172
+ // returned default version, and CC API attaches a synthetic
38173
+ // `LatestVersionNumber` / `DefaultVersionNumber` next to the template
38174
+ // data that drift would surface as drift on the parent. Until an SDK
38175
+ // provider strips those, deny.
38176
+ "AWS::EC2::LaunchTemplate": "CC API returns version-bumped LaunchTemplateData with synthetic LatestVersionNumber that diverges from the CFn input shape"
38177
+ };
38178
+
38179
+ // src/analyzer/cc-api-strip.ts
38180
+ var ALWAYS_STRIPPED_FIELDS = /* @__PURE__ */ new Set([
38181
+ // Timestamps — AWS-managed, change on every modification. Names are
38182
+ // unambiguous: no CFn template ever exposes a "CreationDate" /
38183
+ // "LastModifiedTime" as a settable input.
38184
+ "CreationDate",
38185
+ "CreationTime",
38186
+ "CreatedTime",
38187
+ "CreatedDate",
38188
+ "CreatedAt",
38189
+ "LastModifiedDate",
38190
+ "LastModifiedTime",
38191
+ "LastModified",
38192
+ "LastUpdatedTime",
38193
+ "LastUpdatedDate",
38194
+ "UpdatedAt",
38195
+ // Owner / account / principal info — derived from the calling
38196
+ // principal, never user-set in a CFn template. `CreatedBy` /
38197
+ // `OwnerArn` are unique enough that no settable CFn property
38198
+ // collides with them.
38199
+ "OwnerId",
38200
+ "OwnerAccountId",
38201
+ "CreatedBy",
38202
+ "OwnerArn",
38203
+ // Lambda-specific generated identifiers. `RevisionId` rotates on
38204
+ // every operation; `LastUpdateStatus*` mirror runtime state. None
38205
+ // are settable in a CFn template.
38206
+ "RevisionId",
38207
+ "LastUpdateStatus",
38208
+ "LastUpdateStatusReason",
38209
+ "LastUpdateStatusReasonCode",
38210
+ // CloudFormation/Cloud Control passthrough metadata — never
38211
+ // appears as a settable input in a CFn template body.
38212
+ "StackId",
38213
+ "PhysicalResourceId",
38214
+ "LogicalResourceId"
38215
+ // Notes on intentional EXCLUSIONS:
38216
+ //
38217
+ // `State` / `Status` / `StateReason` / `StatusReason` — these
38218
+ // names ARE used by some CFn types as settable nested properties
38219
+ // (e.g. `AWS::ECS::CapacityProvider.AutoScalingGroupProvider.ManagedScaling.Status`,
38220
+ // `AWS::S3::Bucket.VersioningConfiguration.Status`). Stripping them
38221
+ // globally would cause false-positive drift on a clean stack.
38222
+ // The comparator already ignores AWS-only top-level `Status` values
38223
+ // because state doesn't carry them; only the nested-name-collision
38224
+ // cases would have leaked through, and excluding them here protects
38225
+ // those.
38226
+ //
38227
+ // `Arn` — many CFn types accept `Arn` as a settable property and
38228
+ // cdkd state may record it at create time. Drift on `Arn` is
38229
+ // genuine drift the user wants to see.
38230
+ //
38231
+ // `VersionId` / `GenerationId` / `ETag` — narrow utility (only S3
38232
+ // / KMS / ImageBuilder use these in their Get* responses), and at
38233
+ // least `VersionId` IS a settable input on `AWS::S3::Bucket`'s
38234
+ // versioning config. Per-provider readCurrentState handles them.
38235
+ //
38236
+ // `AccountId` / `StackName` — also collide with settable inputs
38237
+ // on a few CFn types (`AWS::CloudWatch::CrossAccountSharingRule.AccountId`,
38238
+ // `AWS::CloudFormation::HookDefaultVersion.StackName`).
38239
+ //
38240
+ // `StartTime` / `EndTime` — used as settable inputs in scheduling
38241
+ // shapes (e.g. `AWS::AutoScaling::ScheduledAction.StartTime`).
38242
+ ]);
38243
+ function stripCcApiAwsManagedFields(resourceType, awsProps) {
38244
+ return stripWalk(awsProps);
38245
+ }
38246
+ function stripWalk(value) {
38247
+ if (value === null || value === void 0)
38248
+ return value;
38249
+ if (Array.isArray(value)) {
38250
+ return value.map(stripWalk);
38251
+ }
38252
+ if (typeof value === "object") {
38253
+ const out = {};
38254
+ for (const [key, child] of Object.entries(value)) {
38255
+ if (ALWAYS_STRIPPED_FIELDS.has(key))
38256
+ continue;
38257
+ out[key] = stripWalk(child);
38258
+ }
38259
+ return out;
38260
+ }
38261
+ return value;
38262
+ }
38263
+
38053
38264
  // src/cli/commands/drift.ts
38054
38265
  var DriftDetectedError = class _DriftDetectedError extends CdkdError {
38055
38266
  silent = true;
@@ -38092,6 +38303,7 @@ async function driftCommand(stacks, options) {
38092
38303
  const providerRegistry = new ProviderRegistry();
38093
38304
  registerAllProviders(providerRegistry);
38094
38305
  providerRegistry.setCustomResourceResponseBucket(bucket);
38306
+ const ccApiFallback = new CloudControlProvider();
38095
38307
  const stateRefs = await stateBackend.listStacks();
38096
38308
  const targetRefs = resolveTargetRefs(stacks, stateRefs, options);
38097
38309
  const reports = [];
@@ -38105,7 +38317,8 @@ async function driftCommand(stacks, options) {
38105
38317
  ref.stackName,
38106
38318
  ref.region,
38107
38319
  stateBackend,
38108
- providerRegistry
38320
+ providerRegistry,
38321
+ ccApiFallback
38109
38322
  );
38110
38323
  reports.push(report);
38111
38324
  }
@@ -38176,7 +38389,7 @@ function resolveTargetRefs(stacks, stateRefs, options) {
38176
38389
  }
38177
38390
  return out;
38178
38391
  }
38179
- async function runDriftForStack(stackName, region, stateBackend, providerRegistry) {
38392
+ async function runDriftForStack(stackName, region, stateBackend, providerRegistry, ccApiFallback) {
38180
38393
  const result = await stateBackend.getState(stackName, region);
38181
38394
  if (!result) {
38182
38395
  throw new Error(
@@ -38202,27 +38415,39 @@ async function runDriftForStack(stackName, region, stateBackend, providerRegistr
38202
38415
  });
38203
38416
  continue;
38204
38417
  }
38205
- let readCurrentState = provider.readCurrentState?.bind(provider);
38206
- if (!readCurrentState) {
38207
- const ccApiProvider = providerRegistry.getCloudControlProvider?.();
38208
- if (ccApiProvider?.readCurrentState) {
38209
- readCurrentState = ccApiProvider.readCurrentState.bind(ccApiProvider);
38418
+ let aws;
38419
+ if (provider.readCurrentState) {
38420
+ aws = await provider.readCurrentState(
38421
+ resource.physicalId,
38422
+ logicalId,
38423
+ resource.resourceType,
38424
+ resource.properties ?? {}
38425
+ );
38426
+ } else {
38427
+ if (CC_API_FALLBACK_DENY_LIST[resource.resourceType]) {
38428
+ outcomes.push({
38429
+ kind: "unsupported",
38430
+ logicalId,
38431
+ resourceType: resource.resourceType
38432
+ });
38433
+ continue;
38210
38434
  }
38211
- }
38212
- if (!readCurrentState) {
38213
- outcomes.push({
38214
- kind: "unsupported",
38435
+ const ccApiAws = await ccApiFallback.readCurrentState(
38436
+ resource.physicalId,
38215
38437
  logicalId,
38216
- resourceType: resource.resourceType
38217
- });
38218
- continue;
38438
+ resource.resourceType,
38439
+ resource.properties ?? {}
38440
+ );
38441
+ if (ccApiAws === void 0) {
38442
+ outcomes.push({
38443
+ kind: "unsupported",
38444
+ logicalId,
38445
+ resourceType: resource.resourceType
38446
+ });
38447
+ continue;
38448
+ }
38449
+ aws = stripCcApiAwsManagedFields(resource.resourceType, ccApiAws);
38219
38450
  }
38220
- const aws = await readCurrentState(
38221
- resource.physicalId,
38222
- logicalId,
38223
- resource.resourceType,
38224
- resource.properties ?? {}
38225
- );
38226
38451
  if (aws === void 0) {
38227
38452
  outcomes.push({
38228
38453
  kind: "unsupported",
@@ -38356,6 +38581,7 @@ async function runRevert(reports, providerRegistry, stateConfig, awsClients, opt
38356
38581
  const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
38357
38582
  const concurrency = Math.max(1, options.concurrency ?? 4);
38358
38583
  let totalFailed = 0;
38584
+ let totalUnsupported = 0;
38359
38585
  let totalSucceeded = 0;
38360
38586
  for (const report of reports) {
38361
38587
  const driftedOutcomes = report.outcomes.filter(
@@ -38393,10 +38619,17 @@ async function runRevert(reports, providerRegistry, stateConfig, awsClients, opt
38393
38619
  ` \u2713 ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): reverted.`
38394
38620
  );
38395
38621
  } catch (err) {
38622
+ if (err instanceof ResourceUpdateNotSupportedError) {
38623
+ totalUnsupported++;
38624
+ logger.warn(
38625
+ ` \u2298 ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): could not revert \u2014 ${err.message}`
38626
+ );
38627
+ return;
38628
+ }
38396
38629
  totalFailed++;
38397
38630
  const msg = err instanceof Error ? err.message : String(err);
38398
38631
  logger.error(
38399
- ` \u2717 ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): ${msg}`
38632
+ ` \u2717 ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): AWS update failed \u2014 ${msg}`
38400
38633
  );
38401
38634
  }
38402
38635
  });
@@ -38409,11 +38642,21 @@ async function runRevert(reports, providerRegistry, stateConfig, awsClients, opt
38409
38642
  });
38410
38643
  }
38411
38644
  }
38645
+ const summaryParts = [`${totalSucceeded} reverted`];
38646
+ if (totalUnsupported > 0)
38647
+ summaryParts.push(`${totalUnsupported} update-not-supported`);
38648
+ if (totalFailed > 0)
38649
+ summaryParts.push(`${totalFailed} failed`);
38412
38650
  logger.info(`
38413
- Revert summary: ${totalSucceeded} reverted, ${totalFailed} failed.`);
38414
- if (totalFailed > 0) {
38651
+ Revert summary: ${summaryParts.join(", ")}.`);
38652
+ if (totalUnsupported > 0) {
38653
+ logger.warn(
38654
+ `${totalUnsupported} resource(s) cannot be reverted in place \u2014 re-deploy the stack with cdkd deploy --replace, or destroy + redeploy to push the cdkd-state values back into AWS.`
38655
+ );
38656
+ }
38657
+ if (totalFailed > 0 || totalUnsupported > 0) {
38415
38658
  throw new PartialFailureError(
38416
- `Revert completed with ${totalFailed} resource error(s). Re-run 'cdkd drift <stack>' to see the remaining drift, then 'cdkd drift <stack> --revert' to retry.`
38659
+ `Revert completed with ${totalFailed + totalUnsupported} resource error(s) (${totalFailed} AWS update failure(s), ${totalUnsupported} update-not-supported). Re-run 'cdkd drift <stack>' to see the remaining drift, then 'cdkd drift <stack> --revert' to retry.`
38417
38660
  );
38418
38661
  }
38419
38662
  }
@@ -41760,7 +42003,7 @@ function reorderArgs(argv) {
41760
42003
  }
41761
42004
  async function main() {
41762
42005
  const program = new Command14();
41763
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.43.0");
42006
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.45.0");
41764
42007
  program.addCommand(createBootstrapCommand());
41765
42008
  program.addCommand(createSynthCommand());
41766
42009
  program.addCommand(createListCommand());