@go-to-k/cdkd 0.200.0 → 0.201.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as setAwsClients, i as resetAwsClients, r as getAwsClients, t as AwsClients } from "./aws-clients-DWUnLza1.js";
3
- import { $ as uploadCfnTemplate, A as S3StateBackend, At as PATTERN_B_NAME_PROPERTIES, B as Synthesizer, C as assertRegionMatch, Ct as normalizeAwsError, D as DagBuilder, E as DiffCalculator, Et as getLogger, F as buildDockerImage, Ft as withStackName, G as resolveSkipPrefix, H as getLegacyStateBucketName, I as formatDockerLoginError, J as warnDeprecatedNoPrefixCliFlag, K as resolveStateBucketWithDefault, L as getDockerCmd, M as AssetPublisher, Mt as generateResourceName, N as stringifyValue, Nt as generateResourceNameWithFallback, O as TemplateParser, Ot as runStackBuffered, P as WorkGraph, Pt as withSkipPrefix, Q as findLargeInlineResources, R as runDockerForeground, S as CloudControlProvider, T as applyRoleArnIfSet, U as resolveApp, V as getDefaultStateBucketName, W as resolveCaptureObservedState, X as CFN_TEMPLATE_URL_LIMIT, Y as CFN_TEMPLATE_BODY_LIMIT, Z as MIGRATE_TMP_PREFIX, _ as matchesCdkPath, _t as StackHasActiveImportsError, a as withRetry, b as ProviderRegistry, c as bold, ct as LocalMigrateError, d as green, dt as MissingCdkCliError, et as AssemblyReader, f as red, ft as NestedStackChildDirectDestroyError, g as CDK_PATH_TAG, gt as ResourceUpdateNotSupportedError, h as collectInlinePolicyNamesManagedBySiblings, ht as ResourceTimeoutError, i as withResourceDeadline, it as CdkdError, j as shouldRetainResource, jt as PATTERN_B_RESOURCE_TYPES, k as LockManager, kt as getLiveRenderer, l as cyan, lt as LocalStartServiceError, m as IAMRoleProvider, mt as ProvisioningError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as resolveBucketRegion, o as IMPLICIT_DELETE_DEPENDENCIES, p as yellow, pt as PartialFailureError, q as resolveStateBucketWithDefaultAndSource, r as DeployEngine, s as formatResourceLine, st as LocalInvokeBuildError$1, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as gray, v as normalizeAwsTagsToCfn, vt as StackTerminationProtectionError, w as IntrinsicFunctionResolver, wt as withErrorHandling, x as findActionableSilentDrops, y as resolveExplicitPhysicalId, z as runDockerStreaming } from "./deploy-engine-DAPAdI1e.js";
3
+ import { $ as uploadCfnTemplate, A as S3StateBackend, At as PATTERN_B_NAME_PROPERTIES, B as Synthesizer, C as assertRegionMatch, Ct as normalizeAwsError, D as DagBuilder, E as DiffCalculator, Et as getLogger, F as buildDockerImage, Ft as withStackName, G as resolveSkipPrefix, H as getLegacyStateBucketName, I as formatDockerLoginError, J as warnDeprecatedNoPrefixCliFlag, K as resolveStateBucketWithDefault, L as getDockerCmd, M as AssetPublisher, Mt as generateResourceName, N as stringifyValue, Nt as generateResourceNameWithFallback, O as TemplateParser, Ot as runStackBuffered, P as WorkGraph, Pt as withSkipPrefix, Q as findLargeInlineResources, R as runDockerForeground, S as CloudControlProvider, T as applyRoleArnIfSet, U as resolveApp, V as getDefaultStateBucketName, W as resolveCaptureObservedState, X as CFN_TEMPLATE_URL_LIMIT, Y as CFN_TEMPLATE_BODY_LIMIT, Z as MIGRATE_TMP_PREFIX, _ as matchesCdkPath, _t as StackHasActiveImportsError, a as withRetry, b as ProviderRegistry, c as bold, ct as LocalMigrateError, d as green, dt as MissingCdkCliError, et as AssemblyReader, f as red, ft as NestedStackChildDirectDestroyError, g as CDK_PATH_TAG, gt as ResourceUpdateNotSupportedError, h as collectInlinePolicyNamesManagedBySiblings, ht as ResourceTimeoutError, i as withResourceDeadline, it as CdkdError, j as shouldRetainResource, jt as PATTERN_B_RESOURCE_TYPES, k as LockManager, kt as getLiveRenderer, l as cyan, lt as LocalStartServiceError, m as IAMRoleProvider, mt as ProvisioningError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as resolveBucketRegion, o as IMPLICIT_DELETE_DEPENDENCIES, p as yellow, pt as PartialFailureError, q as resolveStateBucketWithDefaultAndSource, r as DeployEngine, s as formatResourceLine, st as LocalInvokeBuildError$1, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as gray, v as normalizeAwsTagsToCfn, vt as StackTerminationProtectionError, w as IntrinsicFunctionResolver, wt as withErrorHandling, x as findActionableSilentDrops, y as resolveExplicitPhysicalId, z as runDockerStreaming } from "./deploy-engine-5CVA5VWR.js";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  import { randomBytes, randomUUID } from "node:crypto";
6
6
  import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListBucketsCommand, ListDirectoryBucketsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
@@ -58,7 +58,7 @@ import { CreateDeliveryStreamCommand, DeleteDeliveryStreamCommand, DescribeDeliv
58
58
  import { AddTagsCommand as AddTagsCommand$1, CloudTrailClient, CreateTrailCommand, DeleteTrailCommand, GetEventSelectorsCommand, GetInsightSelectorsCommand, GetTrailCommand, GetTrailStatusCommand, ListTagsCommand as ListTagsCommand$1, ListTrailsCommand, PutEventSelectorsCommand, PutInsightSelectorsCommand, RemoveTagsCommand as RemoveTagsCommand$1, StartLoggingCommand, StopLoggingCommand, TrailNotFoundException, UpdateTrailCommand } from "@aws-sdk/client-cloudtrail";
59
59
  import { BatchGetProjectsCommand, CodeBuildClient, CreateProjectCommand, DeleteProjectCommand, ListProjectsCommand, ResourceNotFoundException as ResourceNotFoundException$10, UpdateProjectCommand } from "@aws-sdk/client-codebuild";
60
60
  import { CreateVectorBucketCommand, DeleteIndexCommand, DeleteVectorBucketCommand, GetVectorBucketCommand, ListIndexesCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$18, ListVectorBucketsCommand, S3VectorsClient } from "@aws-sdk/client-s3vectors";
61
- import { CreateNamespaceCommand, CreateTableBucketCommand, CreateTableCommand as CreateTableCommand$2, DeleteNamespaceCommand as DeleteNamespaceCommand$1, DeleteTableBucketCommand, DeleteTableCommand as DeleteTableCommand$2, GetTableBucketCommand, GetTableCommand as GetTableCommand$1, ListNamespacesCommand as ListNamespacesCommand$1, ListTableBucketsCommand, ListTablesCommand as ListTablesCommand$1, ListTagsForResourceCommand as ListTagsForResourceCommand$19, NotFoundException as NotFoundException$5, S3TablesClient } from "@aws-sdk/client-s3tables";
61
+ import { CreateNamespaceCommand, CreateTableBucketCommand, CreateTableCommand as CreateTableCommand$2, DeleteNamespaceCommand as DeleteNamespaceCommand$1, DeleteTableBucketCommand, DeleteTableCommand as DeleteTableCommand$2, GetTableBucketCommand, GetTableCommand as GetTableCommand$1, ListNamespacesCommand as ListNamespacesCommand$1, ListTableBucketsCommand, ListTablesCommand as ListTablesCommand$1, ListTagsForResourceCommand as ListTagsForResourceCommand$19, NotFoundException as NotFoundException$5, S3TablesClient, TagResourceCommand as TagResourceCommand$16, UntagResourceCommand as UntagResourceCommand$15 } from "@aws-sdk/client-s3tables";
62
62
  import { AttachLoadBalancerTargetGroupsCommand, AttachLoadBalancersCommand, AttachTrafficSourcesCommand, AutoScalingClient, CreateAutoScalingGroupCommand, CreateOrUpdateTagsCommand, DeleteAutoScalingGroupCommand, DeleteLifecycleHookCommand, DeleteNotificationConfigurationCommand, DeleteTagsCommand as DeleteTagsCommand$1, DescribeAutoScalingGroupsCommand, DescribeLifecycleHooksCommand, DescribeNotificationConfigurationsCommand, DescribeTrafficSourcesCommand, DetachLoadBalancerTargetGroupsCommand, DetachLoadBalancersCommand, DetachTrafficSourcesCommand, DisableMetricsCollectionCommand, EnableMetricsCollectionCommand, PutLifecycleHookCommand, PutNotificationConfigurationCommand, UpdateAutoScalingGroupCommand } from "@aws-sdk/client-auto-scaling";
63
63
  import { Document, Pair, Scalar, YAMLMap, YAMLSeq, parse as parse$1, stringify } from "yaml";
64
64
  import { createLocalStateProvider, getEmbedConfig, isCfnFlagPresent, listTargets, rejectExplicitCfnStackWithMultipleStacks, resolveCfnFallbackRegion, setEmbedConfig, substituteAgainstState, substituteAgainstStateAsync, substituteEnvVarsFromState, substituteEnvVarsFromStateAsync } from "cdk-local";
@@ -17236,7 +17236,7 @@ var CloudFrontDistributionProvider = class {
17236
17236
  const getResponse = await this.cloudFrontClient.send(new GetDistributionCommand({ Id: physicalId }));
17237
17237
  const domainName = getResponse.Distribution?.DomainName ?? "";
17238
17238
  const arn = getResponse.Distribution?.ARN;
17239
- await this.tryApplyTagDiff(arn, previousProperties["Tags"], properties["Tags"], physicalId);
17239
+ await this.applyTagDiff(arn, previousProperties["Tags"], properties["Tags"], physicalId, logicalId, resourceType);
17240
17240
  this.logger.debug(`Updated CloudFront Distribution ${physicalId}`);
17241
17241
  return {
17242
17242
  physicalId,
@@ -17489,32 +17489,44 @@ var CloudFrontDistributionProvider = class {
17489
17489
  };
17490
17490
  }
17491
17491
  /**
17492
- * Apply a tag diff to a distribution, best-effort.
17492
+ * Apply a tag diff to a distribution.
17493
17493
  *
17494
17494
  * CloudFront has no atomic overlay API for tags — `TagResource` adds /
17495
17495
  * overwrites and `UntagResource` removes. Run the removal first, then
17496
17496
  * the upsert, so a same-key value rewrite (which lands in `upserts`)
17497
17497
  * is not accidentally cleared by a stale Untag.
17498
17498
  *
17499
- * Errors are logged but not rethrown `update()` already succeeded
17500
- * the `UpdateDistribution` call before this is invoked, so propagating
17501
- * a tag-side error would flip a successful config update into a deploy
17502
- * failure that triggers an idempotent retry. A `warn` surfaces the
17503
- * unapplied delta to the operator without breaking the deploy.
17499
+ * Errors are RETHROWN as `ProvisioningError` (issue #740 fix) so the
17500
+ * deploy engine sees a failed update() and (a) does NOT write the new
17501
+ * properties.Tags into state the next deploy retries the tag-diff
17502
+ * against the still-old state, (b) surfaces the failure to the user
17503
+ * via the standard error path. The deploy engine's `withRetry` MAY
17504
+ * pick up some transient AWS errors via the cause-message-pattern
17505
+ * match (`retryable-errors.ts`'s `RETRYABLE_ERROR_MESSAGE_PATTERNS`),
17506
+ * but bare HTTP 429 throttles wrapped by update()'s outer catch reach
17507
+ * the classifier two levels deep and slip past its single-level
17508
+ * `.cause` walk — so the **load-bearing retry guarantee is the
17509
+ * next-deploy retry**, not in-deploy retry. This is acceptable: the
17510
+ * user sees the failure, can react, and the next `cdkd deploy`
17511
+ * re-fires the tag-diff against the still-old state cleanly.
17512
+ *
17513
+ * Trade-off: a tag-side failure flips an otherwise-successful
17514
+ * `UpdateDistribution` into a deploy failure, and the retry will
17515
+ * re-issue UpdateDistribution against the (now-current) config —
17516
+ * AWS accepts the no-op idempotently. The cost of that secondary
17517
+ * noise is much lower than the cost of silent tag drift the pre-#740
17518
+ * swallow caused.
17504
17519
  *
17505
17520
  * The ARN is unexpectedly absent only on a hypothetical SDK regression
17506
17521
  * (`GetDistribution` returns ARN as a required string in every SDK
17507
17522
  * shape verified so far). When that happens AND a tag delta exists,
17508
- * log a warn so the silent-drop this PR is closing does not silently
17509
- * resurface; when no delta exists, return without needing ARN.
17523
+ * THROW so the silent-drop does not silently resurface; when no delta
17524
+ * exists, return without needing ARN.
17510
17525
  */
17511
- async tryApplyTagDiff(arn, previousTags, newTags, physicalId) {
17526
+ async applyTagDiff(arn, previousTags, newTags, physicalId, logicalId, resourceType) {
17512
17527
  const { removed, upserts } = this.computeTagDiff(previousTags, newTags);
17513
17528
  if (removed.length === 0 && upserts.length === 0) return;
17514
- if (!arn) {
17515
- this.logger.warn(`CloudFront Distribution ${physicalId}: GetDistribution returned no ARN; skipping tag diff (removed=${removed.length}, upserts=${upserts.length}). Tags on AWS may drift from the template.`);
17516
- return;
17517
- }
17529
+ if (!arn) throw new ProvisioningError(`CloudFront Distribution ${physicalId}: GetDistribution returned no ARN; cannot apply tag diff (removed=${removed.length}, upserts=${upserts.length}). Refusing to silently drop the tag update — retry on next deploy or check SDK version.`, resourceType, logicalId, physicalId);
17518
17530
  try {
17519
17531
  if (removed.length > 0) {
17520
17532
  this.logger.debug(`Untagging CloudFront Distribution ${arn}: ${removed.join(", ")}`);
@@ -17531,7 +17543,8 @@ var CloudFrontDistributionProvider = class {
17531
17543
  }));
17532
17544
  }
17533
17545
  } catch (err) {
17534
- this.logger.warn(`CloudFront Distribution ${physicalId}: tag diff failed (removed=${removed.length}, upserts=${upserts.length}): ${err instanceof Error ? err.message : String(err)}. UpdateDistribution itself succeeded; tags on AWS may drift from the template until the next deploy.`);
17546
+ const cause = err instanceof Error ? err : void 0;
17547
+ throw new ProvisioningError(`CloudFront Distribution ${physicalId}: tag diff failed (removed=${removed.length}, upserts=${upserts.length}): ${err instanceof Error ? err.message : String(err)}. UpdateDistribution succeeded but TagResource/UntagResource did not — state has NOT been updated so the next deploy will retry the tag-diff.`, resourceType, logicalId, physicalId, cause);
17535
17548
  }
17536
17549
  }
17537
17550
  /**
@@ -32005,7 +32018,9 @@ var S3TablesProvider = class {
32005
32018
  "Namespace",
32006
32019
  "TableName",
32007
32020
  "Name",
32008
- "Format"
32021
+ "OpenTableFormat",
32022
+ "Format",
32023
+ "Tags"
32009
32024
  ])]
32010
32025
  ]);
32011
32026
  getClient() {
@@ -32020,12 +32035,13 @@ var S3TablesProvider = class {
32020
32035
  default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId);
32021
32036
  }
32022
32037
  }
32023
- update(logicalId, physicalId, resourceType, _properties, _previousProperties) {
32024
- this.logger.debug(`Update is no-op for ${resourceType} ${logicalId}`);
32025
- return Promise.resolve({
32038
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
32039
+ if (resourceType === "AWS::S3Tables::Table") await this.applyTableTagsDiff(logicalId, physicalId, resourceType, previousProperties["Tags"], properties["Tags"]);
32040
+ else this.logger.debug(`Update is no-op for ${resourceType} ${logicalId}`);
32041
+ return {
32026
32042
  physicalId,
32027
32043
  wasReplaced: false
32028
- });
32044
+ };
32029
32045
  }
32030
32046
  async delete(logicalId, physicalId, resourceType, _properties, context) {
32031
32047
  switch (resourceType) {
@@ -32121,9 +32137,11 @@ var S3TablesProvider = class {
32121
32137
  this.logger.debug(`Creating S3 Tables Namespace ${logicalId}`);
32122
32138
  const tableBucketARN = properties["TableBucketARN"];
32123
32139
  if (!tableBucketARN) throw new ProvisioningError(`TableBucketARN is required for S3 Tables Namespace ${logicalId}`, resourceType, logicalId);
32124
- const namespace = properties["Namespace"];
32125
- if (!namespace || namespace.length === 0) throw new ProvisioningError(`Namespace is required for S3 Tables Namespace ${logicalId}`, resourceType, logicalId);
32126
- const namespaceName = namespace[0];
32140
+ const rawNs = properties["Namespace"];
32141
+ let namespaceName;
32142
+ if (Array.isArray(rawNs) && rawNs.length > 0 && typeof rawNs[0] === "string") namespaceName = rawNs[0];
32143
+ else if (typeof rawNs === "string" && rawNs.length > 0) namespaceName = rawNs;
32144
+ if (!namespaceName) throw new ProvisioningError(`Namespace is required for S3 Tables Namespace ${logicalId}`, resourceType, logicalId);
32127
32145
  try {
32128
32146
  await this.getClient().send(new CreateNamespaceCommand({
32129
32147
  tableBucketARN,
@@ -32168,20 +32186,24 @@ var S3TablesProvider = class {
32168
32186
  if (!namespace) throw new ProvisioningError(`Namespace is required for S3 Tables Table ${logicalId}`, resourceType, logicalId);
32169
32187
  const name = properties["TableName"] ?? properties["Name"];
32170
32188
  if (!name) throw new ProvisioningError(`TableName is required for S3 Tables Table ${logicalId}`, resourceType, logicalId);
32171
- const format = properties["Format"];
32172
- if (!format) throw new ProvisioningError(`Format is required for S3 Tables Table ${logicalId}`, resourceType, logicalId);
32189
+ const format = properties["OpenTableFormat"] ?? properties["Format"];
32190
+ if (!format) throw new ProvisioningError(`OpenTableFormat is required for S3 Tables Table ${logicalId}`, resourceType, logicalId);
32191
+ const tags = this.cfnTagsToSdkMap(properties["Tags"]);
32173
32192
  try {
32174
- await this.getClient().send(new CreateTableCommand$2({
32193
+ const response = await this.getClient().send(new CreateTableCommand$2({
32175
32194
  tableBucketARN,
32176
32195
  namespace,
32177
32196
  name,
32178
- format
32197
+ format,
32198
+ ...tags !== void 0 && { tags }
32179
32199
  }));
32180
32200
  const physicalId = `${tableBucketARN}|${namespace}|${name}`;
32201
+ if (!response.tableARN) throw new ProvisioningError(`CreateTable did not return a tableARN for ${logicalId} (${physicalId}) — refusing to record an empty TableARN attribute`, resourceType, logicalId, physicalId);
32202
+ const tableARN = response.tableARN;
32181
32203
  this.logger.debug(`Successfully created S3 Tables Table ${logicalId}: ${physicalId}`);
32182
32204
  return {
32183
32205
  physicalId,
32184
- attributes: {}
32206
+ attributes: { TableARN: tableARN }
32185
32207
  };
32186
32208
  } catch (error) {
32187
32209
  const cause = error instanceof Error ? error : void 0;
@@ -32233,7 +32255,7 @@ var S3TablesProvider = class {
32233
32255
  if (!tableBucketARN || !namespaceName) return void 0;
32234
32256
  return {
32235
32257
  TableBucketARN: tableBucketARN,
32236
- Namespace: [namespaceName]
32258
+ Namespace: namespaceName
32237
32259
  };
32238
32260
  }
32239
32261
  async readTableCurrentState(physicalId) {
@@ -32259,7 +32281,11 @@ var S3TablesProvider = class {
32259
32281
  Name: tableNameValue,
32260
32282
  TableName: tableNameValue
32261
32283
  };
32262
- if (resp.format !== void 0) result["Format"] = resp.format;
32284
+ if (resp.format !== void 0) {
32285
+ result["OpenTableFormat"] = resp.format;
32286
+ result["Format"] = resp.format;
32287
+ }
32288
+ result["Tags"] = resp.tableARN ? await this.readTagsBestEffort(resp.tableARN) : [];
32263
32289
  return result;
32264
32290
  }
32265
32291
  /**
@@ -32416,6 +32442,131 @@ var S3TablesProvider = class {
32416
32442
  throw new ProvisioningError(`Failed to delete S3 Tables Table ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
32417
32443
  }
32418
32444
  }
32445
+ /**
32446
+ * Convert CFn `Tags: [{ Key, Value }]` to the S3Tables SDK's
32447
+ * `Record<string, string>` shape. Returns `undefined` when the input
32448
+ * is absent, empty, or invalid (so the caller can omit the field
32449
+ * from CreateTableCommand — the SDK rejects an empty `tags: {}` map
32450
+ * with InvalidRequestException). Entries missing a `Key` are skipped;
32451
+ * a missing `Value` is normalized to `''` (matches the on-AWS
32452
+ * representation — empty-string tag values are legal).
32453
+ */
32454
+ cfnTagsToSdkMap(value) {
32455
+ if (!Array.isArray(value) || value.length === 0) return void 0;
32456
+ const map = {};
32457
+ for (const entry of value) {
32458
+ if (!entry || typeof entry !== "object") continue;
32459
+ const key = entry.Key;
32460
+ if (typeof key !== "string" || key.length === 0) continue;
32461
+ const raw = entry.Value;
32462
+ if (typeof raw === "string") map[key] = raw;
32463
+ else if (raw === void 0 || raw === null) map[key] = "";
32464
+ else if (typeof raw === "number" || typeof raw === "boolean") map[key] = String(raw);
32465
+ else continue;
32466
+ }
32467
+ return Object.keys(map).length > 0 ? map : void 0;
32468
+ }
32469
+ /**
32470
+ * Look up a table's real AWS ARN given its cdkd-compound physical
32471
+ * id parts. The real ARN is opaque (NOT `<bucketArn>/table/<ns>/<name>`
32472
+ * — that shape returns BadRequestException) and only AWS knows it,
32473
+ * so we call `GetTable` and pull `tableARN` from the response. Used
32474
+ * by the tag-diff path in update() and the readback's tag-fetch leg.
32475
+ * Returns null if the table is gone (NotFoundException → caller can
32476
+ * skip the tag op gracefully).
32477
+ */
32478
+ async lookupTableArn(tableBucketARN, namespace, name) {
32479
+ try {
32480
+ return (await this.getClient().send(new GetTableCommand$1({
32481
+ tableBucketARN,
32482
+ namespace,
32483
+ name
32484
+ }))).tableARN ?? null;
32485
+ } catch (err) {
32486
+ if (err instanceof NotFoundException$5) return null;
32487
+ throw err;
32488
+ }
32489
+ }
32490
+ /**
32491
+ * Best-effort tag readback. ListTagsForResource against a freshly-
32492
+ * created table can briefly 404 due to eventual consistency; emit
32493
+ * `[]` on any failure rather than propagate (matches the S3Vectors /
32494
+ * CloudFront patterns and keeps the drift comparator happy).
32495
+ */
32496
+ async readTagsBestEffort(resourceArn) {
32497
+ try {
32498
+ const tags = (await this.getClient().send(new ListTagsForResourceCommand$19({ resourceArn }))).tags ?? {};
32499
+ const out = [];
32500
+ for (const [Key, Value] of Object.entries(tags)) out.push({
32501
+ Key,
32502
+ Value
32503
+ });
32504
+ return out;
32505
+ } catch (err) {
32506
+ this.logger.debug(`readTagsBestEffort: ListTagsForResource failed for ${resourceArn}: ${err instanceof Error ? err.message : String(err)} — emitting Tags: []`);
32507
+ return [];
32508
+ }
32509
+ }
32510
+ /**
32511
+ * Apply a tag-diff against a Table resource ARN: keys present in
32512
+ * `previousTags` but absent / value-changed in `newTags` go through
32513
+ * `UntagResource`, then the full upsert set (additions + value
32514
+ * rewrites) goes through `TagResource`. Removal runs FIRST so a
32515
+ * value-only rewrite on key K isn't accidentally cleared by a stale
32516
+ * UntagResource pass (matches the CloudFront / S3Vectors pattern).
32517
+ *
32518
+ * Tag-side failures THROW `ProvisioningError` (issue #740 fix): a
32519
+ * silent swallow would let the deploy engine write the new properties.Tags
32520
+ * to state as if applied, and the next deploy's diff (template Tags
32521
+ * vs new state Tags) sees no change → tag-diff never re-fires → AWS
32522
+ * tags stay stale forever. Throwing means: (a) state is NOT written,
32523
+ * (b) the next deploy retries the tag-diff against the still-old
32524
+ * state. The deploy engine's `withRetry` MAY pick up transient AWS
32525
+ * errors via the cause-message-pattern match in `retryable-errors.ts`,
32526
+ * but bare HTTP 429 throttles can slip past the classifier's
32527
+ * single-level `.cause` walk depending on wrapping — so the
32528
+ * **load-bearing retry guarantee is the next-deploy retry**, not
32529
+ * in-deploy retry. For the S3Tables Table case `update()` is
32530
+ * otherwise a no-op (the Table itself is immutable), so a tag-side
32531
+ * throw cleanly turns the whole update into a retry without
32532
+ * side-effects.
32533
+ *
32534
+ * The malformed-physicalId / GetTable-NotFound branches throw too
32535
+ * — both indicate a state-vs-AWS divergence the user needs to see,
32536
+ * not silently skip past.
32537
+ */
32538
+ async applyTableTagsDiff(logicalId, physicalId, resourceType, previousTags, newTags) {
32539
+ const prev = this.cfnTagsToSdkMap(previousTags) ?? {};
32540
+ const next = this.cfnTagsToSdkMap(newTags) ?? {};
32541
+ const removedKeys = Object.keys(prev).filter((k) => !(k in next));
32542
+ const upserts = {};
32543
+ for (const [k, v] of Object.entries(next)) if (prev[k] !== v) upserts[k] = v;
32544
+ if (removedKeys.length === 0 && Object.keys(upserts).length === 0) return;
32545
+ const parts = physicalId.split("|");
32546
+ if (parts.length < 3) throw new ProvisioningError(`applyTableTagsDiff: cannot derive table ARN from physicalId '${physicalId}' (expected '<bucketArn>|<namespace>|<name>') — refusing to silently drop the tag update`, resourceType, logicalId, physicalId);
32547
+ const [tableBucketARN, namespace, name] = parts;
32548
+ if (!tableBucketARN || !namespace || !name) throw new ProvisioningError(`applyTableTagsDiff: cannot derive table ARN from malformed physicalId '${physicalId}' (empty part after split) — refusing to silently drop the tag update`, resourceType, logicalId, physicalId);
32549
+ const resourceArn = await this.lookupTableArn(tableBucketARN, namespace, name);
32550
+ if (!resourceArn) throw new ProvisioningError(`applyTableTagsDiff: GetTable returned no tableARN for ${physicalId} — table is gone or state is out-of-sync. Refusing to silently drop the tag update (run 'cdkd state orphan ${logicalId}' to clean up if the table was deleted out-of-band).`, resourceType, logicalId, physicalId);
32551
+ if (removedKeys.length > 0) try {
32552
+ await this.getClient().send(new UntagResourceCommand$15({
32553
+ resourceArn,
32554
+ tagKeys: removedKeys
32555
+ }));
32556
+ } catch (err) {
32557
+ const cause = err instanceof Error ? err : void 0;
32558
+ throw new ProvisioningError(`applyTableTagsDiff: UntagResource failed for ${resourceArn} (keys: ${removedKeys.join(", ")}): ${err instanceof Error ? err.message : String(err)}. State has NOT been updated so the next deploy will retry the tag-diff.`, resourceType, logicalId, physicalId, cause);
32559
+ }
32560
+ if (Object.keys(upserts).length > 0) try {
32561
+ await this.getClient().send(new TagResourceCommand$16({
32562
+ resourceArn,
32563
+ tags: upserts
32564
+ }));
32565
+ } catch (err) {
32566
+ const cause = err instanceof Error ? err : void 0;
32567
+ throw new ProvisioningError(`applyTableTagsDiff: TagResource failed for ${resourceArn} (keys: ${Object.keys(upserts).join(", ")}): ${err instanceof Error ? err.message : String(err)}. State has NOT been updated so the next deploy will retry the tag-diff.`, resourceType, logicalId, physicalId, cause);
32568
+ }
32569
+ }
32419
32570
  };
32420
32571
 
32421
32572
  //#endregion
@@ -51726,7 +51877,7 @@ function reorderArgs(argv) {
51726
51877
  */
51727
51878
  async function main() {
51728
51879
  const program = new Command();
51729
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.200.0");
51880
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.201.1");
51730
51881
  program.addCommand(createBootstrapCommand());
51731
51882
  program.addCommand(createSynthCommand());
51732
51883
  program.addCommand(createListCommand());