@go-to-k/cdkd 0.36.0 → 0.37.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/README.md CHANGED
@@ -208,8 +208,15 @@ alias cdkd="node $(pwd)/dist/cli.js"
208
208
 
209
209
  ## Quick Start
210
210
 
211
+ > **First-time setup**: cdkd requires a one-time `cdkd bootstrap` per AWS
212
+ > account before any other command will work — it creates the S3 state
213
+ > bucket (`cdkd-state-{accountId}`) that cdkd uses to track deployed
214
+ > resources. This is separate from `cdk bootstrap` (which sets up the
215
+ > CDK asset bucket / ECR repo and is also required — see
216
+ > [Prerequisites](#prerequisites)).
217
+
211
218
  ```bash
212
- # Bootstrap (creates S3 state bucket - only needed once per account/region)
219
+ # Bootstrap (creates S3 state bucket — one-time setup, once per AWS account)
213
220
  cdkd bootstrap
214
221
 
215
222
  # List stacks in the CDK app
package/dist/cli.js CHANGED
@@ -9028,6 +9028,85 @@ var IAMRoleProvider = class {
9028
9028
  throw err;
9029
9029
  }
9030
9030
  }
9031
+ /**
9032
+ * Read the AWS-current IAM role configuration in CFn-property shape.
9033
+ *
9034
+ * Issues `GetRole` for the top-level role configuration and
9035
+ * `ListRolePolicies` + `ListAttachedRolePolicies` for inline / managed
9036
+ * policy *names*. AWS URL-decodes `AssumeRolePolicyDocument` for us
9037
+ * when it surfaces — we re-parse it as JSON so the comparator can match
9038
+ * against state's already-parsed object.
9039
+ *
9040
+ * Coverage and shape decisions:
9041
+ * - `RoleName`, `Description`, `MaxSessionDuration`, `Path`,
9042
+ * `PermissionsBoundary` — straight from `Role.*`.
9043
+ * - `AssumeRolePolicyDocument` — `Role.AssumeRolePolicyDocument` is a
9044
+ * URL-encoded JSON string; we URL-decode + JSON-parse so cdkd state's
9045
+ * object form compares cleanly. (Both shapes — string and object — are
9046
+ * accepted by `create()`, but state typically stores the parsed object
9047
+ * after intrinsic resolution.)
9048
+ * - `ManagedPolicyArns` — array of ARN strings from
9049
+ * `ListAttachedRolePolicies`.
9050
+ * - `Policies` (inline policies with `PolicyDocument` bodies) is
9051
+ * intentionally omitted: surfacing names without bodies guarantees a
9052
+ * PolicyDocument-shaped drift on every role, and fetching every body
9053
+ * costs one extra `GetRolePolicy` per inline policy. Out of scope for
9054
+ * v1 — drift detection on inline IAM policy bodies can ship in a
9055
+ * follow-up.
9056
+ * - `Tags` is omitted for the same reason as Lambda's tags handling
9057
+ * (CDK auto-injects `aws:cdk:path` and the shape decision belongs in a
9058
+ * dedicated tags PR).
9059
+ *
9060
+ * Returns `undefined` when the role is gone (`NoSuchEntityException`).
9061
+ */
9062
+ async readCurrentState(physicalId, _logicalId, _resourceType) {
9063
+ let role;
9064
+ try {
9065
+ const resp = await this.iamClient.send(new GetRoleCommand({ RoleName: physicalId }));
9066
+ role = resp.Role;
9067
+ } catch (err) {
9068
+ if (err instanceof NoSuchEntityException)
9069
+ return void 0;
9070
+ throw err;
9071
+ }
9072
+ if (!role)
9073
+ return void 0;
9074
+ const result = {};
9075
+ if (role.RoleName !== void 0)
9076
+ result["RoleName"] = role.RoleName;
9077
+ if (role.Description !== void 0 && role.Description !== "") {
9078
+ result["Description"] = role.Description;
9079
+ }
9080
+ if (role.MaxSessionDuration !== void 0) {
9081
+ result["MaxSessionDuration"] = role.MaxSessionDuration;
9082
+ }
9083
+ if (role.Path !== void 0)
9084
+ result["Path"] = role.Path;
9085
+ if (role.PermissionsBoundary?.PermissionsBoundaryArn !== void 0) {
9086
+ result["PermissionsBoundary"] = role.PermissionsBoundary.PermissionsBoundaryArn;
9087
+ }
9088
+ if (role.AssumeRolePolicyDocument) {
9089
+ try {
9090
+ result["AssumeRolePolicyDocument"] = JSON.parse(
9091
+ decodeURIComponent(role.AssumeRolePolicyDocument)
9092
+ );
9093
+ } catch {
9094
+ result["AssumeRolePolicyDocument"] = role.AssumeRolePolicyDocument;
9095
+ }
9096
+ }
9097
+ try {
9098
+ const attached = await this.iamClient.send(
9099
+ new ListAttachedRolePoliciesCommand({ RoleName: physicalId })
9100
+ );
9101
+ const arns = (attached.AttachedPolicies ?? []).map((p) => p.PolicyArn).filter((arn) => !!arn);
9102
+ if (arns.length > 0)
9103
+ result["ManagedPolicyArns"] = arns;
9104
+ } catch (err) {
9105
+ if (!(err instanceof NoSuchEntityException))
9106
+ throw err;
9107
+ }
9108
+ return result;
9109
+ }
9031
9110
  /**
9032
9111
  * Adopt an existing IAM role into cdkd state.
9033
9112
  *
@@ -10831,7 +10910,10 @@ import {
10831
10910
  PutBucketInventoryConfigurationCommand,
10832
10911
  PutBucketReplicationCommand,
10833
10912
  PutObjectLockConfigurationCommand,
10913
+ GetBucketEncryptionCommand,
10834
10914
  GetBucketTaggingCommand,
10915
+ GetBucketVersioningCommand,
10916
+ GetPublicAccessBlockCommand,
10835
10917
  NoSuchBucket,
10836
10918
  ListObjectVersionsCommand,
10837
10919
  DeleteObjectsCommand
@@ -11688,6 +11770,119 @@ var S3BucketProvider = class {
11688
11770
  );
11689
11771
  }
11690
11772
  }
11773
+ /**
11774
+ * Read the AWS-current S3 bucket configuration in CFn-property shape.
11775
+ *
11776
+ * Issues a small handful of independent S3 GET calls and stitches them
11777
+ * into a single CFn-shaped object. Each call can throw a "feature not
11778
+ * configured" error (`NoSuchBucketConfiguration`,
11779
+ * `ServerSideEncryptionConfigurationNotFoundError`, `NoSuchTagSet`,
11780
+ * `NoSuchPublicAccessBlockConfiguration`) — those are caught individually
11781
+ * and the corresponding key is omitted from the result, NOT treated as
11782
+ * the bucket being absent.
11783
+ *
11784
+ * Only the bucket-gone case (`NoSuchBucket`, HTTP 404 from `HeadBucket`)
11785
+ * returns `undefined`.
11786
+ *
11787
+ * Coverage: `BucketName`, `VersioningConfiguration`, `BucketEncryption`,
11788
+ * `PublicAccessBlockConfiguration`, `Tags`. Other configuration
11789
+ * properties (Lifecycle, CORS, Website, Logging, Notification,
11790
+ * Replication, ObjectLock, Accelerate, Metrics/Analytics/IntelligentTier/
11791
+ * Inventory) are out of scope for v1 — they each need their own GET +
11792
+ * shape mapping; CC API drift detection picks them up via `GetResource`
11793
+ * once a user works through the SDK provider boundary.
11794
+ */
11795
+ async readCurrentState(physicalId, _logicalId, _resourceType) {
11796
+ try {
11797
+ await this.s3Client.send(new HeadBucketCommand3({ Bucket: physicalId }));
11798
+ } catch (err) {
11799
+ const e = err;
11800
+ if (err instanceof NoSuchBucket || e.name === "NotFound" || e.name === "NoSuchBucket" || e.$metadata?.httpStatusCode === 404) {
11801
+ return void 0;
11802
+ }
11803
+ throw err;
11804
+ }
11805
+ const result = {
11806
+ BucketName: physicalId
11807
+ };
11808
+ {
11809
+ const resp = await this.s3Client.send(new GetBucketVersioningCommand({ Bucket: physicalId }));
11810
+ if (resp.Status) {
11811
+ result["VersioningConfiguration"] = { Status: resp.Status };
11812
+ }
11813
+ }
11814
+ try {
11815
+ const resp = await this.s3Client.send(new GetBucketEncryptionCommand({ Bucket: physicalId }));
11816
+ const rules = resp.ServerSideEncryptionConfiguration?.Rules;
11817
+ if (rules && rules.length > 0) {
11818
+ result["BucketEncryption"] = {
11819
+ ServerSideEncryptionConfiguration: rules.map((rule) => {
11820
+ const out = {};
11821
+ const sse = rule.ApplyServerSideEncryptionByDefault;
11822
+ if (sse) {
11823
+ const sseOut = {};
11824
+ if (sse.SSEAlgorithm !== void 0)
11825
+ sseOut["SSEAlgorithm"] = sse.SSEAlgorithm;
11826
+ if (sse.KMSMasterKeyID !== void 0)
11827
+ sseOut["KMSMasterKeyID"] = sse.KMSMasterKeyID;
11828
+ out["ServerSideEncryptionByDefault"] = sseOut;
11829
+ }
11830
+ if (rule.BucketKeyEnabled !== void 0)
11831
+ out["BucketKeyEnabled"] = rule.BucketKeyEnabled;
11832
+ return out;
11833
+ })
11834
+ };
11835
+ }
11836
+ } catch (err) {
11837
+ const e = err;
11838
+ if (e.name !== "ServerSideEncryptionConfigurationNotFoundError") {
11839
+ throw err;
11840
+ }
11841
+ }
11842
+ try {
11843
+ const resp = await this.s3Client.send(
11844
+ new GetPublicAccessBlockCommand({ Bucket: physicalId })
11845
+ );
11846
+ const cfg = resp.PublicAccessBlockConfiguration;
11847
+ if (cfg) {
11848
+ const out = {};
11849
+ if (cfg.BlockPublicAcls !== void 0)
11850
+ out["BlockPublicAcls"] = cfg.BlockPublicAcls;
11851
+ if (cfg.BlockPublicPolicy !== void 0)
11852
+ out["BlockPublicPolicy"] = cfg.BlockPublicPolicy;
11853
+ if (cfg.IgnorePublicAcls !== void 0)
11854
+ out["IgnorePublicAcls"] = cfg.IgnorePublicAcls;
11855
+ if (cfg.RestrictPublicBuckets !== void 0) {
11856
+ out["RestrictPublicBuckets"] = cfg.RestrictPublicBuckets;
11857
+ }
11858
+ if (Object.keys(out).length > 0) {
11859
+ result["PublicAccessBlockConfiguration"] = out;
11860
+ }
11861
+ }
11862
+ } catch (err) {
11863
+ const e = err;
11864
+ if (e.name !== "NoSuchPublicAccessBlockConfiguration") {
11865
+ throw err;
11866
+ }
11867
+ }
11868
+ try {
11869
+ const resp = await this.s3Client.send(new GetBucketTaggingCommand({ Bucket: physicalId }));
11870
+ if (resp.TagSet && resp.TagSet.length > 0) {
11871
+ const tags = resp.TagSet.filter((t) => t.Key && !t.Key.startsWith("aws:")).map((t) => ({
11872
+ Key: t.Key,
11873
+ Value: t.Value
11874
+ }));
11875
+ if (tags.length > 0)
11876
+ result["Tags"] = tags;
11877
+ }
11878
+ } catch (err) {
11879
+ const e = err;
11880
+ if (e.name !== "NoSuchTagSet") {
11881
+ throw err;
11882
+ }
11883
+ }
11884
+ return result;
11885
+ }
11691
11886
  /**
11692
11887
  * Adopt an existing S3 bucket into cdkd state.
11693
11888
  *
@@ -12238,6 +12433,90 @@ var SQSQueueProvider = class {
12238
12433
  return void 0;
12239
12434
  }
12240
12435
  }
12436
+ /**
12437
+ * Read the AWS-current SQS queue configuration in CFn-property shape.
12438
+ *
12439
+ * Issues `GetQueueAttributes` for every attribute that maps back to a
12440
+ * cdkd-managed CFn property. AWS returns ALL attribute values as strings;
12441
+ * we type-coerce numeric attributes back to numbers and parse
12442
+ * `RedrivePolicy` from JSON so the comparator matches cdkd state's
12443
+ * already-typed values.
12444
+ *
12445
+ * `QueueName` is derived from the URL tail (the `physicalId` is the
12446
+ * queue URL), not surfaced by `GetQueueAttributes`.
12447
+ *
12448
+ * `Tags` is omitted: `ListQueueTags` is a separate call and tag drift is
12449
+ * generally less interesting than configuration drift; the `aws:cdk:path`
12450
+ * shape question is also out of scope here.
12451
+ *
12452
+ * Returns `undefined` when the queue is gone (`QueueDoesNotExist`).
12453
+ */
12454
+ async readCurrentState(physicalId, _logicalId, _resourceType) {
12455
+ let attributes;
12456
+ try {
12457
+ const resp = await this.sqsClient.send(
12458
+ new GetQueueAttributesCommand({
12459
+ QueueUrl: physicalId,
12460
+ AttributeNames: ["All"]
12461
+ })
12462
+ );
12463
+ attributes = resp.Attributes;
12464
+ } catch (err) {
12465
+ if (err instanceof QueueDoesNotExist)
12466
+ return void 0;
12467
+ throw err;
12468
+ }
12469
+ if (!attributes)
12470
+ return void 0;
12471
+ const result = {};
12472
+ const tail = physicalId.substring(physicalId.lastIndexOf("/") + 1);
12473
+ if (tail)
12474
+ result["QueueName"] = tail;
12475
+ const numeric = [
12476
+ "VisibilityTimeout",
12477
+ "MaximumMessageSize",
12478
+ "MessageRetentionPeriod",
12479
+ "DelaySeconds",
12480
+ "ReceiveMessageWaitTimeSeconds",
12481
+ "KmsDataKeyReusePeriodSeconds"
12482
+ ];
12483
+ for (const key of numeric) {
12484
+ const v = attributes[key];
12485
+ if (v !== void 0) {
12486
+ const n = Number(v);
12487
+ if (!Number.isNaN(n))
12488
+ result[key] = n;
12489
+ }
12490
+ }
12491
+ const bool = [
12492
+ "FifoQueue",
12493
+ "ContentBasedDeduplication",
12494
+ "SqsManagedSseEnabled"
12495
+ ];
12496
+ for (const key of bool) {
12497
+ const v = attributes[key];
12498
+ if (v !== void 0)
12499
+ result[key] = v === "true";
12500
+ }
12501
+ const str = [
12502
+ "KmsMasterKeyId",
12503
+ "DeduplicationScope",
12504
+ "FifoThroughputLimit"
12505
+ ];
12506
+ for (const key of str) {
12507
+ const v = attributes[key];
12508
+ if (v !== void 0)
12509
+ result[key] = v;
12510
+ }
12511
+ if (attributes["RedrivePolicy"]) {
12512
+ try {
12513
+ result["RedrivePolicy"] = JSON.parse(attributes["RedrivePolicy"]);
12514
+ } catch {
12515
+ result["RedrivePolicy"] = attributes["RedrivePolicy"];
12516
+ }
12517
+ }
12518
+ return result;
12519
+ }
12241
12520
  /**
12242
12521
  * Adopt an existing SQS queue into cdkd state.
12243
12522
  *
@@ -12795,6 +13074,76 @@ var SNSTopicProvider = class {
12795
13074
  return void 0;
12796
13075
  }
12797
13076
  }
13077
+ /**
13078
+ * Read the AWS-current SNS topic configuration in CFn-property shape.
13079
+ *
13080
+ * Issues `GetTopicAttributes` for the topic-level configuration. AWS
13081
+ * returns ALL attribute values as strings; we type-coerce booleans back
13082
+ * to booleans and parse `ArchivePolicy` / `DataProtectionPolicy` from
13083
+ * JSON strings so the comparator matches cdkd state's typed values.
13084
+ *
13085
+ * `TopicName` is derived from the ARN tail (the `physicalId` is the
13086
+ * topic ARN).
13087
+ *
13088
+ * `Tags` and `DeliveryStatusLogging` are intentionally omitted:
13089
+ * `ListTagsForResource` is a separate call, and `DeliveryStatusLogging`
13090
+ * fans out into per-protocol attributes (`{Protocol}SuccessFeedbackRoleArn`,
13091
+ * etc.) whose round-trip back to the CFn array shape needs more thought
13092
+ * than fits in this PR.
13093
+ *
13094
+ * `Subscription` is omitted because CDK manages it via separate
13095
+ * `AWS::SNS::Subscription` resources, not as a Topic property.
13096
+ *
13097
+ * Returns `undefined` when the topic is gone (`NotFoundException`).
13098
+ */
13099
+ async readCurrentState(physicalId, _logicalId, _resourceType) {
13100
+ let attrs;
13101
+ try {
13102
+ const resp = await this.snsClient.send(
13103
+ new GetTopicAttributesCommand({ TopicArn: physicalId })
13104
+ );
13105
+ attrs = resp.Attributes;
13106
+ } catch (err) {
13107
+ if (err instanceof NotFoundException)
13108
+ return void 0;
13109
+ throw err;
13110
+ }
13111
+ if (!attrs)
13112
+ return void 0;
13113
+ const result = {};
13114
+ const tail = physicalId.substring(physicalId.lastIndexOf(":") + 1);
13115
+ if (tail)
13116
+ result["TopicName"] = tail;
13117
+ const bool = ["FifoTopic", "ContentBasedDeduplication"];
13118
+ for (const key of bool) {
13119
+ const v = attrs[key];
13120
+ if (v !== void 0)
13121
+ result[key] = v === "true";
13122
+ }
13123
+ const str = [
13124
+ "DisplayName",
13125
+ "KmsMasterKeyId",
13126
+ "TracingConfig",
13127
+ "SignatureVersion",
13128
+ "FifoThroughputScope"
13129
+ ];
13130
+ for (const key of str) {
13131
+ const v = attrs[key];
13132
+ if (v !== void 0 && v !== "")
13133
+ result[key] = v;
13134
+ }
13135
+ for (const key of ["ArchivePolicy", "DataProtectionPolicy"]) {
13136
+ const v = attrs[key];
13137
+ if (v) {
13138
+ try {
13139
+ result[key] = JSON.parse(v);
13140
+ } catch {
13141
+ result[key] = v;
13142
+ }
13143
+ }
13144
+ }
13145
+ return result;
13146
+ }
12798
13147
  /**
12799
13148
  * Adopt an existing SNS topic into cdkd state.
12800
13149
  *
@@ -13880,6 +14229,93 @@ var LambdaFunctionProvider = class {
13880
14229
  throw err;
13881
14230
  }
13882
14231
  }
14232
+ /**
14233
+ * Read the AWS-current Lambda function configuration in CFn-property shape.
14234
+ *
14235
+ * Issues a single `GetFunction` and surfaces the same property keys
14236
+ * `create()` accepts (`Runtime`, `Handler`, `Role`, `Timeout`, `MemorySize`,
14237
+ * `Description`, `Environment`, `Layers`, `Architectures`, `PackageType`,
14238
+ * `TracingConfig`, `EphemeralStorage`, `VpcConfig`, plus the physical
14239
+ * `FunctionName`). The drift comparator only descends into keys present in
14240
+ * cdkd state, so AWS-managed fields (timestamps, FunctionArn, RevisionId,
14241
+ * etc.) are filtered at compare time — we still avoid serializing them on
14242
+ * the wire.
14243
+ *
14244
+ * `Code` is intentionally omitted: `GetFunction` returns a pre-signed S3
14245
+ * URL for the deployed code, not the asset hash cdkd state holds, so they
14246
+ * could never match. Lambda code drift is best detected separately (the
14247
+ * function's `CodeSha256` does live in `GetFunction` but is not what
14248
+ * cdkd's `Code: { S3Bucket, S3Key }` state property carries).
14249
+ *
14250
+ * `Tags` is omitted as well: `GetFunction` returns Tags as an object map,
14251
+ * while CFn / cdkd state holds them as `[{Key, Value}]`. Re-shaping
14252
+ * accurately requires deciding how to handle the auto-injected
14253
+ * `aws:cdk:path` tag, which is out of scope for this PR. Tag drift is
14254
+ * typically less interesting than configuration drift.
14255
+ *
14256
+ * Returns `undefined` when the function is gone (`ResourceNotFoundException`).
14257
+ */
14258
+ async readCurrentState(physicalId, _logicalId, _resourceType) {
14259
+ try {
14260
+ const resp = await this.lambdaClient.send(
14261
+ new GetFunctionCommand({ FunctionName: physicalId })
14262
+ );
14263
+ const cfg = resp.Configuration;
14264
+ if (!cfg)
14265
+ return void 0;
14266
+ const result = {};
14267
+ if (cfg.FunctionName !== void 0)
14268
+ result["FunctionName"] = cfg.FunctionName;
14269
+ if (cfg.Runtime !== void 0)
14270
+ result["Runtime"] = cfg.Runtime;
14271
+ if (cfg.Handler !== void 0)
14272
+ result["Handler"] = cfg.Handler;
14273
+ if (cfg.Role !== void 0)
14274
+ result["Role"] = cfg.Role;
14275
+ if (cfg.Timeout !== void 0)
14276
+ result["Timeout"] = cfg.Timeout;
14277
+ if (cfg.MemorySize !== void 0)
14278
+ result["MemorySize"] = cfg.MemorySize;
14279
+ if (cfg.Description !== void 0 && cfg.Description !== "") {
14280
+ result["Description"] = cfg.Description;
14281
+ }
14282
+ if (cfg.Environment?.Variables) {
14283
+ result["Environment"] = { Variables: cfg.Environment.Variables };
14284
+ }
14285
+ if (cfg.Layers && cfg.Layers.length > 0) {
14286
+ result["Layers"] = cfg.Layers.map((l) => l.Arn).filter((arn) => !!arn);
14287
+ }
14288
+ if (cfg.Architectures && cfg.Architectures.length > 0) {
14289
+ result["Architectures"] = [...cfg.Architectures];
14290
+ }
14291
+ if (cfg.PackageType !== void 0)
14292
+ result["PackageType"] = cfg.PackageType;
14293
+ if (cfg.TracingConfig?.Mode !== void 0) {
14294
+ result["TracingConfig"] = { Mode: cfg.TracingConfig.Mode };
14295
+ }
14296
+ if (cfg.EphemeralStorage?.Size !== void 0) {
14297
+ result["EphemeralStorage"] = { Size: cfg.EphemeralStorage.Size };
14298
+ }
14299
+ if (cfg.VpcConfig) {
14300
+ const vpc = {};
14301
+ if (cfg.VpcConfig.SubnetIds)
14302
+ vpc["SubnetIds"] = [...cfg.VpcConfig.SubnetIds];
14303
+ if (cfg.VpcConfig.SecurityGroupIds) {
14304
+ vpc["SecurityGroupIds"] = [...cfg.VpcConfig.SecurityGroupIds];
14305
+ }
14306
+ if (cfg.VpcConfig.Ipv6AllowedForDualStack !== void 0) {
14307
+ vpc["Ipv6AllowedForDualStack"] = cfg.VpcConfig.Ipv6AllowedForDualStack;
14308
+ }
14309
+ if (Object.keys(vpc).length > 0)
14310
+ result["VpcConfig"] = vpc;
14311
+ }
14312
+ return result;
14313
+ } catch (err) {
14314
+ if (err instanceof ResourceNotFoundException)
14315
+ return void 0;
14316
+ throw err;
14317
+ }
14318
+ }
13883
14319
  /**
13884
14320
  * Adopt an existing Lambda function into cdkd state.
13885
14321
  *
@@ -15055,6 +15491,90 @@ var DynamoDBTableProvider = class {
15055
15491
  throw err;
15056
15492
  }
15057
15493
  }
15494
+ /**
15495
+ * Read the AWS-current DynamoDB table configuration in CFn-property shape.
15496
+ *
15497
+ * `DescribeTable` returns every field cdkd manages in one call. AWS uses
15498
+ * the same property names CFn does (KeySchema, AttributeDefinitions,
15499
+ * BillingModeSummary.BillingMode, ProvisionedThroughput, etc.) — the only
15500
+ * shape differences are wrapping:
15501
+ * - BillingMode lives under `BillingModeSummary.BillingMode` in the API
15502
+ * response, but the CFn property is a flat `BillingMode` string.
15503
+ * - StreamSpecification's CFn shape includes only `StreamViewType`; the
15504
+ * API response carries `StreamEnabled` too. We surface both since the
15505
+ * drift comparator only descends into keys present in state.
15506
+ * - GSI / LSI in the API response include `IndexStatus`, `ItemCount` and
15507
+ * sizing fields that cdkd never sets; the comparator filters them.
15508
+ *
15509
+ * Returns `undefined` when the table is gone (`ResourceNotFoundException`).
15510
+ *
15511
+ * Tags are intentionally omitted: `ListTagsOfResource` is a separate call
15512
+ * and tag drift is generally less interesting than table-config drift;
15513
+ * including it would also force a tag-shape decision on the
15514
+ * `aws:cdk:path` auto-tag, which is out of scope here.
15515
+ */
15516
+ async readCurrentState(physicalId, _logicalId, _resourceType) {
15517
+ try {
15518
+ const resp = await this.dynamoDBClient.send(
15519
+ new DescribeTableCommand2({ TableName: physicalId })
15520
+ );
15521
+ const table = resp.Table;
15522
+ if (!table)
15523
+ return void 0;
15524
+ const result = {};
15525
+ if (table.TableName !== void 0)
15526
+ result["TableName"] = table.TableName;
15527
+ if (table.KeySchema)
15528
+ result["KeySchema"] = table.KeySchema;
15529
+ if (table.AttributeDefinitions) {
15530
+ result["AttributeDefinitions"] = table.AttributeDefinitions;
15531
+ }
15532
+ if (table.BillingModeSummary?.BillingMode) {
15533
+ result["BillingMode"] = table.BillingModeSummary.BillingMode;
15534
+ }
15535
+ if (table.ProvisionedThroughput) {
15536
+ result["ProvisionedThroughput"] = {
15537
+ ReadCapacityUnits: table.ProvisionedThroughput.ReadCapacityUnits,
15538
+ WriteCapacityUnits: table.ProvisionedThroughput.WriteCapacityUnits
15539
+ };
15540
+ }
15541
+ if (table.StreamSpecification) {
15542
+ result["StreamSpecification"] = {
15543
+ StreamEnabled: table.StreamSpecification.StreamEnabled,
15544
+ StreamViewType: table.StreamSpecification.StreamViewType
15545
+ };
15546
+ }
15547
+ if (table.GlobalSecondaryIndexes && table.GlobalSecondaryIndexes.length > 0) {
15548
+ result["GlobalSecondaryIndexes"] = table.GlobalSecondaryIndexes;
15549
+ }
15550
+ if (table.LocalSecondaryIndexes && table.LocalSecondaryIndexes.length > 0) {
15551
+ result["LocalSecondaryIndexes"] = table.LocalSecondaryIndexes;
15552
+ }
15553
+ if (table.SSEDescription) {
15554
+ const sse = {};
15555
+ if (table.SSEDescription.Status === "ENABLED")
15556
+ sse["SSEEnabled"] = true;
15557
+ if (table.SSEDescription.KMSMasterKeyArn !== void 0) {
15558
+ sse["KMSMasterKeyId"] = table.SSEDescription.KMSMasterKeyArn;
15559
+ }
15560
+ if (table.SSEDescription.SSEType !== void 0)
15561
+ sse["SSEType"] = table.SSEDescription.SSEType;
15562
+ if (Object.keys(sse).length > 0)
15563
+ result["SSESpecification"] = sse;
15564
+ }
15565
+ if (table.DeletionProtectionEnabled !== void 0) {
15566
+ result["DeletionProtectionEnabled"] = table.DeletionProtectionEnabled;
15567
+ }
15568
+ if (table.TableClassSummary?.TableClass) {
15569
+ result["TableClass"] = table.TableClassSummary.TableClass;
15570
+ }
15571
+ return result;
15572
+ } catch (err) {
15573
+ if (err instanceof ResourceNotFoundException6)
15574
+ return void 0;
15575
+ throw err;
15576
+ }
15577
+ }
15058
15578
  /**
15059
15579
  * Adopt an existing DynamoDB table into cdkd state.
15060
15580
  *
@@ -15359,6 +15879,48 @@ var LogsLogGroupProvider = class {
15359
15879
  }
15360
15880
  return this.buildArn(physicalId);
15361
15881
  }
15882
+ /**
15883
+ * Read the AWS-current log group configuration in CFn-property shape.
15884
+ *
15885
+ * Issues `DescribeLogGroups` filtered by exact name and picks the first
15886
+ * (and only) match. AWS uses camelCase field names in the API response
15887
+ * (`logGroupName`, `kmsKeyId`, `retentionInDays`); we map them back to
15888
+ * the CFn-cased keys cdkd state holds (`LogGroupName`, `KmsKeyId`,
15889
+ * `RetentionInDays`).
15890
+ *
15891
+ * Coverage: `LogGroupName`, `KmsKeyId`, `RetentionInDays`,
15892
+ * `LogGroupClass`. Other handledProperties (`DataProtectionPolicy`,
15893
+ * `Tags`, `FieldIndexPolicies`, `ResourcePolicyDocument`,
15894
+ * `DeletionProtectionEnabled`, `BearerTokenAuthenticationEnabled`) need
15895
+ * their own per-property API call and are out of scope for v1.
15896
+ *
15897
+ * Returns `undefined` when the log group is gone.
15898
+ */
15899
+ async readCurrentState(physicalId, _logicalId, _resourceType) {
15900
+ try {
15901
+ const resp = await this.logsClient.send(
15902
+ new DescribeLogGroupsCommand({ logGroupNamePrefix: physicalId })
15903
+ );
15904
+ const found = resp.logGroups?.find((g) => g.logGroupName === physicalId);
15905
+ if (!found)
15906
+ return void 0;
15907
+ const result = {};
15908
+ if (found.logGroupName !== void 0)
15909
+ result["LogGroupName"] = found.logGroupName;
15910
+ if (found.kmsKeyId !== void 0)
15911
+ result["KmsKeyId"] = found.kmsKeyId;
15912
+ if (found.retentionInDays !== void 0) {
15913
+ result["RetentionInDays"] = found.retentionInDays;
15914
+ }
15915
+ if (found.logGroupClass !== void 0)
15916
+ result["LogGroupClass"] = found.logGroupClass;
15917
+ return result;
15918
+ } catch (err) {
15919
+ if (err instanceof ResourceNotFoundException7)
15920
+ return void 0;
15921
+ throw err;
15922
+ }
15923
+ }
15362
15924
  /**
15363
15925
  * Adopt an existing CloudWatch Logs log group into cdkd state.
15364
15926
  *
@@ -36982,7 +37544,7 @@ function reorderArgs(argv) {
36982
37544
  }
36983
37545
  async function main() {
36984
37546
  const program = new Command14();
36985
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.36.0");
37547
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.37.0");
36986
37548
  program.addCommand(createBootstrapCommand());
36987
37549
  program.addCommand(createSynthCommand());
36988
37550
  program.addCommand(createListCommand());