@go-to-k/cdkd 0.199.0 → 0.201.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
@@ -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-DGQOHJpW.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,11 +58,11 @@ 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";
65
- import { A2A_CONTAINER_PORT, A2A_PATH, AGENTCORE_A2A_PROTOCOL, AGENTCORE_AGUI_PROTOCOL, AGENTCORE_MCP_PROTOCOL, ConnectionRegistry, EcsTaskResolutionError, HOST_GATEWAY_MIN_VERSION, LocalInvokeBuildError, MCP_CONTAINER_PORT, MCP_PATH, a2aInvokeOnce, addCommonEcsServiceOptions, architectureToPlatform, attachAuthorizers, attachStageContext, availableApiIdentifiers, bufferToBody, buildAgentCoreCodeImage, buildCognitoJwksUrl, buildConnectEvent, buildContainerImage, buildCorsConfigByApiId, buildCorsConfigFromCloudFrontChain, buildDisconnectEvent, buildJwksUrlFromIssuer, buildMessageEvent, buildMgmtEndpointEnvUrl, buildStageMap, createAuthorizerCache, createFileWatcher, createJwksCache, createWatchPredicates, defaultCredentialsLoader, derivePseudoParametersFromRegion, discoverRoutes, discoverWebSocketApis, downloadAndExtractS3Bundle, filterRoutesByApiIdentifier, groupRoutesByServer, handleConnectionsRequest, invokeAgentCore, invokeAgentCoreWs, isApplicationLoadBalancer, materializeLayerFromArn, mcpInvokeOnce, parseConnectionsPath, parseSelectionExpressionPath, pickAgentCoreCandidateStack, probeHostGatewaySupport, readMtlsMaterialsFromDisk, resolveAgentCoreTarget, resolveAlbFrontDoor, resolveEnvVars, resolveRuntimeCodeMountPath, resolveRuntimeFileExtension, resolveRuntimeImage, resolveSingleTarget, resolveWatchConfig, runEcsServiceEmulator, signAgentCoreInvocation, startApiServer, substituteImagePlaceholders, tryResolveImageFnJoin, verifyJwtViaDiscovery, waitForAgentCorePing } from "cdk-local/internal";
65
+ import { A2A_CONTAINER_PORT, A2A_PATH, AGENTCORE_A2A_PROTOCOL, AGENTCORE_AGUI_PROTOCOL, AGENTCORE_MCP_PROTOCOL, ConnectionRegistry, EcsTaskResolutionError, HOST_GATEWAY_MIN_VERSION, LocalInvokeBuildError, MCP_CONTAINER_PORT, MCP_PATH, a2aInvokeOnce, addAlbSpecificOptions, addCommonEcsServiceOptions, albStrategy, architectureToPlatform, attachAuthorizers, attachStageContext, availableApiIdentifiers, bufferToBody, buildAgentCoreCodeImage, buildCognitoJwksUrl, buildConnectEvent, buildContainerImage, buildCorsConfigByApiId, buildCorsConfigFromCloudFrontChain, buildDisconnectEvent, buildJwksUrlFromIssuer, buildMessageEvent, buildMgmtEndpointEnvUrl, buildStageMap, createAuthorizerCache, createFileWatcher, createJwksCache, createWatchPredicates, defaultCredentialsLoader, derivePseudoParametersFromRegion, discoverRoutes, discoverWebSocketApis, downloadAndExtractS3Bundle, filterRoutesByApiIdentifier, groupRoutesByServer, handleConnectionsRequest, invokeAgentCore, invokeAgentCoreWs, materializeLayerFromArn, mcpInvokeOnce, parseConnectionsPath, parseSelectionExpressionPath, pickAgentCoreCandidateStack, probeHostGatewaySupport, readMtlsMaterialsFromDisk, resolveAgentCoreTarget, resolveEnvVars, resolveRuntimeCodeMountPath, resolveRuntimeFileExtension, resolveRuntimeImage, resolveSingleTarget, resolveWatchConfig, runEcsServiceEmulator, signAgentCoreInvocation, startApiServer, substituteImagePlaceholders, tryResolveImageFnJoin, verifyJwtViaDiscovery, waitForAgentCorePing } from "cdk-local/internal";
66
66
  import { createServer } from "node:net";
67
67
  import { promisify } from "node:util";
68
68
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
@@ -19823,7 +19823,15 @@ var RDSProvider = class {
19823
19823
  "DBClusterIdentifier",
19824
19824
  "DBSubnetGroupName",
19825
19825
  "PubliclyAccessible",
19826
- "Tags"
19826
+ "Tags",
19827
+ "AllocatedStorage",
19828
+ "DeletionProtection",
19829
+ "EngineVersion",
19830
+ "MasterUsername",
19831
+ "MasterUserPassword",
19832
+ "Port",
19833
+ "StorageEncrypted",
19834
+ "VPCSecurityGroups"
19827
19835
  ])]
19828
19836
  ]);
19829
19837
  unhandledByDesign = new Map([["AWS::RDS::DBCluster", new Map([["DeleteAutomatedBackups", "cdkd hardcodes SkipFinalSnapshot=true on destroy; this CFn lifecycle flag has no equivalent on the runtime path"]])], ["AWS::RDS::DBInstance", new Map([
@@ -20077,6 +20085,14 @@ var RDSProvider = class {
20077
20085
  DBClusterIdentifier: properties["DBClusterIdentifier"],
20078
20086
  DBSubnetGroupName: properties["DBSubnetGroupName"],
20079
20087
  PubliclyAccessible: properties["PubliclyAccessible"],
20088
+ ...properties["AllocatedStorage"] !== void 0 && { AllocatedStorage: Number(properties["AllocatedStorage"]) },
20089
+ ...properties["MasterUsername"] !== void 0 && { MasterUsername: properties["MasterUsername"] },
20090
+ ...properties["DeletionProtection"] !== void 0 && { DeletionProtection: properties["DeletionProtection"] },
20091
+ ...properties["EngineVersion"] !== void 0 && { EngineVersion: properties["EngineVersion"] },
20092
+ ...properties["Port"] !== void 0 && { Port: Number(properties["Port"]) },
20093
+ ...properties["MasterUserPassword"] !== void 0 && { MasterUserPassword: properties["MasterUserPassword"] },
20094
+ ...properties["StorageEncrypted"] !== void 0 && { StorageEncrypted: properties["StorageEncrypted"] },
20095
+ ...properties["VPCSecurityGroups"] !== void 0 && { VpcSecurityGroupIds: properties["VPCSecurityGroups"] },
20080
20096
  ...tags.length > 0 && { Tags: tags }
20081
20097
  }))).DBInstance) throw new Error("CreateDBInstance did not return DBInstance");
20082
20098
  this.logger.debug(`Successfully created DBInstance ${logicalId}: ${dbInstanceIdentifier}`);
@@ -20099,11 +20115,23 @@ var RDSProvider = class {
20099
20115
  async updateDBInstance(logicalId, physicalId, resourceType, properties, previousProperties) {
20100
20116
  this.logger.debug(`Updating DBInstance ${logicalId}: ${physicalId}`);
20101
20117
  try {
20118
+ const newEngineVersion = properties["EngineVersion"];
20119
+ const prevEngineVersion = previousProperties["EngineVersion"];
20120
+ const allowMajorVersionUpgrade = newEngineVersion !== void 0 && newEngineVersion !== prevEngineVersion && prevEngineVersion !== void 0 && newEngineVersion.split(".")[0] !== prevEngineVersion.split(".")[0];
20102
20121
  await this.getClient().send(new ModifyDBInstanceCommand({
20103
20122
  DBInstanceIdentifier: physicalId,
20104
20123
  DBInstanceClass: properties["DBInstanceClass"],
20105
20124
  PubliclyAccessible: properties["PubliclyAccessible"],
20106
- ApplyImmediately: true
20125
+ ApplyImmediately: true,
20126
+ ...properties["AllocatedStorage"] !== void 0 && { AllocatedStorage: Number(properties["AllocatedStorage"]) },
20127
+ ...properties["DeletionProtection"] !== void 0 && { DeletionProtection: properties["DeletionProtection"] },
20128
+ ...newEngineVersion !== void 0 && {
20129
+ EngineVersion: newEngineVersion,
20130
+ ...allowMajorVersionUpgrade && { AllowMajorVersionUpgrade: true }
20131
+ },
20132
+ ...properties["Port"] !== void 0 && { DBPortNumber: Number(properties["Port"]) },
20133
+ ...properties["MasterUserPassword"] !== void 0 && { MasterUserPassword: properties["MasterUserPassword"] },
20134
+ ...properties["VPCSecurityGroups"] !== void 0 && { VpcSecurityGroupIds: properties["VPCSecurityGroups"] }
20107
20135
  }));
20108
20136
  this.logger.debug(`Successfully updated DBInstance ${logicalId}`);
20109
20137
  const described = await this.describeDBInstance(physicalId);
@@ -20336,6 +20364,14 @@ var RDSProvider = class {
20336
20364
  if (inst.DBClusterIdentifier !== void 0) result["DBClusterIdentifier"] = inst.DBClusterIdentifier;
20337
20365
  if (inst.DBSubnetGroup?.DBSubnetGroupName !== void 0) result["DBSubnetGroupName"] = inst.DBSubnetGroup.DBSubnetGroupName;
20338
20366
  if (inst.PubliclyAccessible !== void 0) result["PubliclyAccessible"] = inst.PubliclyAccessible;
20367
+ if (inst.AllocatedStorage !== void 0) result["AllocatedStorage"] = inst.AllocatedStorage;
20368
+ if (inst.MasterUsername !== void 0) result["MasterUsername"] = inst.MasterUsername;
20369
+ if (inst.DeletionProtection !== void 0) result["DeletionProtection"] = inst.DeletionProtection;
20370
+ if (inst.EngineVersion !== void 0) result["EngineVersion"] = inst.EngineVersion;
20371
+ if (inst.Endpoint?.Port !== void 0) result["Port"] = inst.Endpoint.Port;
20372
+ if (inst.StorageEncrypted !== void 0) result["StorageEncrypted"] = inst.StorageEncrypted;
20373
+ const sgIds = (inst.VpcSecurityGroups ?? []).map((sg) => sg.VpcSecurityGroupId).filter((id) => !!id);
20374
+ if (sgIds.length > 0) result["VPCSecurityGroups"] = sgIds;
20339
20375
  if (inst.DBInstanceArn) await this.attachTags(result, inst.DBInstanceArn);
20340
20376
  return result;
20341
20377
  }
@@ -31969,7 +32005,9 @@ var S3TablesProvider = class {
31969
32005
  "Namespace",
31970
32006
  "TableName",
31971
32007
  "Name",
31972
- "Format"
32008
+ "OpenTableFormat",
32009
+ "Format",
32010
+ "Tags"
31973
32011
  ])]
31974
32012
  ]);
31975
32013
  getClient() {
@@ -31984,12 +32022,13 @@ var S3TablesProvider = class {
31984
32022
  default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId);
31985
32023
  }
31986
32024
  }
31987
- update(logicalId, physicalId, resourceType, _properties, _previousProperties) {
31988
- this.logger.debug(`Update is no-op for ${resourceType} ${logicalId}`);
31989
- return Promise.resolve({
32025
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
32026
+ if (resourceType === "AWS::S3Tables::Table") await this.applyTableTagsDiff(physicalId, previousProperties["Tags"], properties["Tags"]);
32027
+ else this.logger.debug(`Update is no-op for ${resourceType} ${logicalId}`);
32028
+ return {
31990
32029
  physicalId,
31991
32030
  wasReplaced: false
31992
- });
32031
+ };
31993
32032
  }
31994
32033
  async delete(logicalId, physicalId, resourceType, _properties, context) {
31995
32034
  switch (resourceType) {
@@ -32085,9 +32124,11 @@ var S3TablesProvider = class {
32085
32124
  this.logger.debug(`Creating S3 Tables Namespace ${logicalId}`);
32086
32125
  const tableBucketARN = properties["TableBucketARN"];
32087
32126
  if (!tableBucketARN) throw new ProvisioningError(`TableBucketARN is required for S3 Tables Namespace ${logicalId}`, resourceType, logicalId);
32088
- const namespace = properties["Namespace"];
32089
- if (!namespace || namespace.length === 0) throw new ProvisioningError(`Namespace is required for S3 Tables Namespace ${logicalId}`, resourceType, logicalId);
32090
- const namespaceName = namespace[0];
32127
+ const rawNs = properties["Namespace"];
32128
+ let namespaceName;
32129
+ if (Array.isArray(rawNs) && rawNs.length > 0 && typeof rawNs[0] === "string") namespaceName = rawNs[0];
32130
+ else if (typeof rawNs === "string" && rawNs.length > 0) namespaceName = rawNs;
32131
+ if (!namespaceName) throw new ProvisioningError(`Namespace is required for S3 Tables Namespace ${logicalId}`, resourceType, logicalId);
32091
32132
  try {
32092
32133
  await this.getClient().send(new CreateNamespaceCommand({
32093
32134
  tableBucketARN,
@@ -32132,20 +32173,24 @@ var S3TablesProvider = class {
32132
32173
  if (!namespace) throw new ProvisioningError(`Namespace is required for S3 Tables Table ${logicalId}`, resourceType, logicalId);
32133
32174
  const name = properties["TableName"] ?? properties["Name"];
32134
32175
  if (!name) throw new ProvisioningError(`TableName is required for S3 Tables Table ${logicalId}`, resourceType, logicalId);
32135
- const format = properties["Format"];
32136
- if (!format) throw new ProvisioningError(`Format is required for S3 Tables Table ${logicalId}`, resourceType, logicalId);
32176
+ const format = properties["OpenTableFormat"] ?? properties["Format"];
32177
+ if (!format) throw new ProvisioningError(`OpenTableFormat is required for S3 Tables Table ${logicalId}`, resourceType, logicalId);
32178
+ const tags = this.cfnTagsToSdkMap(properties["Tags"]);
32137
32179
  try {
32138
- await this.getClient().send(new CreateTableCommand$2({
32180
+ const response = await this.getClient().send(new CreateTableCommand$2({
32139
32181
  tableBucketARN,
32140
32182
  namespace,
32141
32183
  name,
32142
- format
32184
+ format,
32185
+ ...tags !== void 0 && { tags }
32143
32186
  }));
32144
32187
  const physicalId = `${tableBucketARN}|${namespace}|${name}`;
32188
+ 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);
32189
+ const tableARN = response.tableARN;
32145
32190
  this.logger.debug(`Successfully created S3 Tables Table ${logicalId}: ${physicalId}`);
32146
32191
  return {
32147
32192
  physicalId,
32148
- attributes: {}
32193
+ attributes: { TableARN: tableARN }
32149
32194
  };
32150
32195
  } catch (error) {
32151
32196
  const cause = error instanceof Error ? error : void 0;
@@ -32197,7 +32242,7 @@ var S3TablesProvider = class {
32197
32242
  if (!tableBucketARN || !namespaceName) return void 0;
32198
32243
  return {
32199
32244
  TableBucketARN: tableBucketARN,
32200
- Namespace: [namespaceName]
32245
+ Namespace: namespaceName
32201
32246
  };
32202
32247
  }
32203
32248
  async readTableCurrentState(physicalId) {
@@ -32223,7 +32268,11 @@ var S3TablesProvider = class {
32223
32268
  Name: tableNameValue,
32224
32269
  TableName: tableNameValue
32225
32270
  };
32226
- if (resp.format !== void 0) result["Format"] = resp.format;
32271
+ if (resp.format !== void 0) {
32272
+ result["OpenTableFormat"] = resp.format;
32273
+ result["Format"] = resp.format;
32274
+ }
32275
+ result["Tags"] = resp.tableARN ? await this.readTagsBestEffort(resp.tableARN) : [];
32227
32276
  return result;
32228
32277
  }
32229
32278
  /**
@@ -32380,6 +32429,123 @@ var S3TablesProvider = class {
32380
32429
  throw new ProvisioningError(`Failed to delete S3 Tables Table ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
32381
32430
  }
32382
32431
  }
32432
+ /**
32433
+ * Convert CFn `Tags: [{ Key, Value }]` to the S3Tables SDK's
32434
+ * `Record<string, string>` shape. Returns `undefined` when the input
32435
+ * is absent, empty, or invalid (so the caller can omit the field
32436
+ * from CreateTableCommand — the SDK rejects an empty `tags: {}` map
32437
+ * with InvalidRequestException). Entries missing a `Key` are skipped;
32438
+ * a missing `Value` is normalized to `''` (matches the on-AWS
32439
+ * representation — empty-string tag values are legal).
32440
+ */
32441
+ cfnTagsToSdkMap(value) {
32442
+ if (!Array.isArray(value) || value.length === 0) return void 0;
32443
+ const map = {};
32444
+ for (const entry of value) {
32445
+ if (!entry || typeof entry !== "object") continue;
32446
+ const key = entry.Key;
32447
+ if (typeof key !== "string" || key.length === 0) continue;
32448
+ const raw = entry.Value;
32449
+ if (typeof raw === "string") map[key] = raw;
32450
+ else if (raw === void 0 || raw === null) map[key] = "";
32451
+ else if (typeof raw === "number" || typeof raw === "boolean") map[key] = String(raw);
32452
+ else continue;
32453
+ }
32454
+ return Object.keys(map).length > 0 ? map : void 0;
32455
+ }
32456
+ /**
32457
+ * Look up a table's real AWS ARN given its cdkd-compound physical
32458
+ * id parts. The real ARN is opaque (NOT `<bucketArn>/table/<ns>/<name>`
32459
+ * — that shape returns BadRequestException) and only AWS knows it,
32460
+ * so we call `GetTable` and pull `tableARN` from the response. Used
32461
+ * by the tag-diff path in update() and the readback's tag-fetch leg.
32462
+ * Returns null if the table is gone (NotFoundException → caller can
32463
+ * skip the tag op gracefully).
32464
+ */
32465
+ async lookupTableArn(tableBucketARN, namespace, name) {
32466
+ try {
32467
+ return (await this.getClient().send(new GetTableCommand$1({
32468
+ tableBucketARN,
32469
+ namespace,
32470
+ name
32471
+ }))).tableARN ?? null;
32472
+ } catch (err) {
32473
+ if (err instanceof NotFoundException$5) return null;
32474
+ throw err;
32475
+ }
32476
+ }
32477
+ /**
32478
+ * Best-effort tag readback. ListTagsForResource against a freshly-
32479
+ * created table can briefly 404 due to eventual consistency; emit
32480
+ * `[]` on any failure rather than propagate (matches the S3Vectors /
32481
+ * CloudFront patterns and keeps the drift comparator happy).
32482
+ */
32483
+ async readTagsBestEffort(resourceArn) {
32484
+ try {
32485
+ const tags = (await this.getClient().send(new ListTagsForResourceCommand$19({ resourceArn }))).tags ?? {};
32486
+ const out = [];
32487
+ for (const [Key, Value] of Object.entries(tags)) out.push({
32488
+ Key,
32489
+ Value
32490
+ });
32491
+ return out;
32492
+ } catch (err) {
32493
+ this.logger.debug(`readTagsBestEffort: ListTagsForResource failed for ${resourceArn}: ${err instanceof Error ? err.message : String(err)} — emitting Tags: []`);
32494
+ return [];
32495
+ }
32496
+ }
32497
+ /**
32498
+ * Apply a tag-diff against a Table resource ARN: keys present in
32499
+ * `previousTags` but absent / value-changed in `newTags` go through
32500
+ * `UntagResource`, then the full upsert set (additions + value
32501
+ * rewrites) goes through `TagResource`. Removal runs FIRST so a
32502
+ * value-only rewrite on key K isn't accidentally cleared by a stale
32503
+ * UntagResource pass (matches the CloudFront / S3Vectors pattern).
32504
+ *
32505
+ * Tag ops are best-effort post-step in update(): a tag-side failure
32506
+ * MUST NOT flip the deploy engine into a retry that would re-issue
32507
+ * the no-op `update()` body. Log at warn instead so the user sees
32508
+ * the unapplied delta but the deploy still progresses.
32509
+ */
32510
+ async applyTableTagsDiff(physicalId, previousTags, newTags) {
32511
+ const parts = physicalId.split("|");
32512
+ if (parts.length < 3) {
32513
+ this.logger.warn(`applyTableTagsDiff: cannot derive table ARN from physicalId '${physicalId}' — skipping tag-diff`);
32514
+ return;
32515
+ }
32516
+ const [tableBucketARN, namespace, name] = parts;
32517
+ if (!tableBucketARN || !namespace || !name) {
32518
+ this.logger.warn(`applyTableTagsDiff: cannot derive table ARN from malformed physicalId '${physicalId}' (empty part after split) — skipping tag-diff`);
32519
+ return;
32520
+ }
32521
+ const prev = this.cfnTagsToSdkMap(previousTags) ?? {};
32522
+ const next = this.cfnTagsToSdkMap(newTags) ?? {};
32523
+ const removedKeys = Object.keys(prev).filter((k) => !(k in next));
32524
+ const upserts = {};
32525
+ for (const [k, v] of Object.entries(next)) if (prev[k] !== v) upserts[k] = v;
32526
+ if (removedKeys.length === 0 && Object.keys(upserts).length === 0) return;
32527
+ const resourceArn = await this.lookupTableArn(tableBucketARN, namespace, name);
32528
+ if (!resourceArn) {
32529
+ this.logger.warn(`applyTableTagsDiff: GetTable returned no tableARN for ${physicalId} — skipping tag-diff (table gone? state out-of-sync?)`);
32530
+ return;
32531
+ }
32532
+ if (removedKeys.length > 0) try {
32533
+ await this.getClient().send(new UntagResourceCommand$15({
32534
+ resourceArn,
32535
+ tagKeys: removedKeys
32536
+ }));
32537
+ } catch (err) {
32538
+ this.logger.warn(`applyTableTagsDiff: UntagResource failed for ${resourceArn} (keys: ${removedKeys.join(", ")}): ${err instanceof Error ? err.message : String(err)}`);
32539
+ }
32540
+ if (Object.keys(upserts).length > 0) try {
32541
+ await this.getClient().send(new TagResourceCommand$16({
32542
+ resourceArn,
32543
+ tags: upserts
32544
+ }));
32545
+ } catch (err) {
32546
+ this.logger.warn(`applyTableTagsDiff: TagResource failed for ${resourceArn} (keys: ${Object.keys(upserts).join(", ")}): ${err instanceof Error ? err.message : String(err)}`);
32547
+ }
32548
+ }
32383
32549
  };
32384
32550
 
32385
32551
  //#endregion
@@ -43308,7 +43474,7 @@ function parseTarget(target) {
43308
43474
  function resolveLambdaTarget(target, stacks) {
43309
43475
  if (stacks.length === 0) throw new LocalInvokeResolutionError("No stacks found in the synthesized assembly.");
43310
43476
  const parsed = parseTarget(target);
43311
- const stack = pickStack$2(parsed, stacks);
43477
+ const stack = pickStack$1(parsed, stacks);
43312
43478
  const template = stack.template;
43313
43479
  const resources = template.Resources ?? {};
43314
43480
  let match;
@@ -43342,7 +43508,7 @@ function resolveLambdaTarget(target, stacks) {
43342
43508
  * user may omit the stack prefix. Otherwise an explicit stack pattern is
43343
43509
  * required.
43344
43510
  */
43345
- function pickStack$2(parsed, stacks) {
43511
+ function pickStack$1(parsed, stacks) {
43346
43512
  if (parsed.stackPattern === null) {
43347
43513
  if (stacks.length === 1) return stacks[0];
43348
43514
  throw new LocalInvokeResolutionError(`Multiple stacks in app, target '${parsed.pathOrId}' is missing a stack prefix. Use 'StackName:${parsed.pathOrId}' or 'StackName/...' (path form). Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`);
@@ -43965,7 +44131,7 @@ function parseEcsTarget(target) {
43965
44131
  function resolveEcsTaskTarget(target, stacks, context) {
43966
44132
  if (stacks.length === 0) throw new EcsTaskResolutionError("No stacks found in the synthesized assembly.");
43967
44133
  const parsed = parseEcsTarget(target);
43968
- const stack = pickStack$1(parsed, stacks);
44134
+ const stack = pickStack(parsed, stacks);
43969
44135
  const resources = stack.template.Resources ?? {};
43970
44136
  let logicalId;
43971
44137
  let resource;
@@ -43986,7 +44152,7 @@ function resolveEcsTaskTarget(target, stacks, context) {
43986
44152
  if (resource.Type !== "AWS::ECS::TaskDefinition") throw new EcsTaskResolutionError(`Resource '${logicalId}' in ${stack.stackName} is ${resource.Type}, not an AWS::ECS::TaskDefinition.`);
43987
44153
  return extractTaskDefinitionProperties(stack, logicalId, resource, context);
43988
44154
  }
43989
- function pickStack$1(parsed, stacks) {
44155
+ function pickStack(parsed, stacks) {
43990
44156
  if (parsed.stackPattern === null) {
43991
44157
  if (stacks.length === 1) return stacks[0];
43992
44158
  throw new EcsTaskResolutionError(`Multiple stacks in app, target '${parsed.pathOrId}' is missing a stack prefix. Use 'StackName:${parsed.pathOrId}' or 'StackName/...' (path form). Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`);
@@ -46580,7 +46746,7 @@ async function localStartApiCommand(target, options) {
46580
46746
  jwksWarnedUrls,
46581
46747
  sigV4CredentialsLoader,
46582
46748
  sigV4WarnedForeignIds,
46583
- sigV4Strict: options.allowUnverifiedSigv4 !== true,
46749
+ sigV4Strict: options.strictSigv4 === true,
46584
46750
  ...defaultRegion && { defaultRegion }
46585
46751
  });
46586
46752
  servers.push({
@@ -46626,7 +46792,7 @@ async function localStartApiCommand(target, options) {
46626
46792
  jwksCache,
46627
46793
  jwksWarnedUrls,
46628
46794
  sigV4WarnedForeignIds,
46629
- sigV4Strict: options.allowUnverifiedSigv4 !== true,
46795
+ sigV4Strict: options.strictSigv4 === true,
46630
46796
  preDispatch: async (req, res) => {
46631
46797
  if (!registryRef) return false;
46632
46798
  return handleManagementRequest(req, res, registryRef.registry);
@@ -47787,7 +47953,7 @@ function resolveMtlsConfig(options) {
47787
47953
  * Builder for the `start-api` subcommand. Wired up by `local.ts`.
47788
47954
  */
47789
47955
  function createLocalStartApiCommand() {
47790
- const startApi = new Command("start-api").description("Run a long-running local HTTP server that maps API Gateway routes (REST v1, HTTP API, Function URL) to Lambda invocations against the AWS Lambda Runtime Interface Emulator (Docker required). Supports Lambda TOKEN/REQUEST authorizers, Cognito User Pool / HTTP v2 JWT authorizers, and AWS_IAM auth (REST v1 `AuthorizationType: AWS_IAM` and Function URL `AuthType: AWS_IAM` — SigV4 signature verification only; IAM policy evaluation is NOT emulated; see https://github.com/go-to-k/cdkd/blob/main/docs/local-emulation.md). When JWKS is unreachable, JWT authorizers fall back to pass-through (every token accepted) with a warn line — local dev fallback. VPC-config Lambdas run locally and surface a warn line at startup; their containers do NOT get attached to the deployed VPC subnets, so calls to private RDS / ElastiCache will fail.").argument("[target]", "Optional API filter. Accepts the bare CDK logical id ('MyHttpApi'; single-stack apps only), stack-qualified logical id ('MyStack:MyHttpApi'), full CDK Construct path ('MyStack/MyHttpApi/Resource'), or an ancestor Construct path that prefix-matches ('MyStack/MyHttpApi'). When omitted, every discovered API gets its own server. Mirrors `cdkd local invoke` / `cdkd local run-task` target syntax.").addOption(new Option("--port <port>", "HTTP server port (default: auto-allocate)").default("0")).addOption(new Option("--host <host>", "Bind address").default("127.0.0.1")).addOption(new Option("--stack <name>", "Stack to start (single-stack apps auto-detect)")).addOption(new Option("--warm", "Pre-start one container per Lambda at server boot").default(false)).addOption(new Option("--per-lambda-concurrency <n>", "Pool size cap per Lambda (default 2, max 4)").default("2")).addOption(new Option("--no-pull", "Skip docker pull (cached image)")).addOption(new Option("--container-host <host>", "IP the host uses to bind/probe the RIE port (must be a numeric IP — `docker run -p <ip>:<port>:8080` rejects hostnames). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--debug-port-base <port>", "Reserve a contiguous --debug-port range (one per Lambda)")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--assume-role <arn-or-pair>", "Assume the Lambda's execution role and forward STS-issued temp creds. Bare <arn> = global default; <LogicalId>=<arn> = per-Lambda override (repeatable). Per-Lambda > global > unset (developer creds passed through).").argParser((raw, prev) => parseAssumeRoleToken(raw, prev))).addOption(new Option("--watch", "Hot-reload: watch the CDK app source tree and re-synth + re-discover routes on a source edit (cdk.out / node_modules / .git excluded; honors cdk.json watch.include / watch.exclude). Off by default; the server keeps the previous version serving when synth fails mid-reload.").default(false)).addOption(new Option("--stage <name>", "Select an API Gateway Stage by its 'StageName'. Default: the first Stage attached to each API. Drives event.stageVariables for both REST v1 and HTTP API v2. NOTE: For HTTP API v2 routes, requestContext.stage is always '$default' regardless of this flag (AWS-side limitation — HTTP API only exposes one stage to the integration event); only event.stageVariables is affected for v2 routes. For REST v1 routes the selected StageName is also threaded into requestContext.stage.")).addOption(new Option("--api <id>", "DEPRECATED — use the positional <target> argument instead. Same accepted forms (bare logical id, stack-qualified, Construct path, ancestor prefix). Will be removed in a future major release.")).addOption(new Option("--layer-role-arn <arn>", "Role to sts:AssumeRole before calling lambda:GetLayerVersion on every literal-ARN entry in Properties.Layers (issue #448). Use only when the dev credentials cannot read the layer — typically cross-account layers. AWS-published public layers (e.g. Lambda Powertools) are readable from every account and need no role.")).addOption(new Option("--from-state", "Read cdkd S3 state for every routed stack and substitute Ref / Fn::GetAtt / Fn::Sub / Fn::Join (and AWS pseudo parameters) in Lambda env vars with the deployed physical IDs / attributes. Off by default — pre-PR warn-and-drop semantics are preserved. Turn on for stacks already deployed via cdkd deploy. Mirrors `cdkd local invoke --from-state` / `cdkd local run-task --from-state`. Re-runs against fresh state on every hot-reload firing (--watch).").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via DescribeStackResources and substitute Ref / Fn::ImportValue in Lambda env vars with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the cdkd stack name per routed stack; pass an explicit value when a single CFn stack should serve every routed stack. Mutually exclusive with --from-state. Fn::GetAtt is warn-and-dropped in v1 (CFn DescribeStackResources does not return per-attribute values).")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-state when the same stack name has state in multiple regions, and with --from-cfn-stack as the CFn client region (cdkd does not have a separate --cfn-stack-region flag).")).addOption(new Option("--mtls-truststore <path>", "PEM-encoded CA bundle for client-certificate verification (mutual TLS). When set, the local server switches from HTTP to HTTPS and the TLS handshake rejects clients whose certificate doesn't chain to one of these CAs. Verified certs are surfaced on the Lambda event under requestContext.identity.clientCert (REST v1) / requestContext.authentication.clientCert (HTTP API v2). Must be set together with --mtls-cert + --mtls-key; partial flag sets are rejected. Generate a CA + server + client cert for local dev: openssl req -x509 -newkey rsa:2048 -nodes -keyout ca-key.pem -out ca.pem -subj \"/CN=cdkd-local-ca\" -days 365; openssl req -newkey rsa:2048 -nodes -keyout server-key.pem -out server-csr.pem -subj \"/CN=localhost\"; openssl x509 -req -in server-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 365; openssl req -newkey rsa:2048 -nodes -keyout client-key.pem -out client-csr.pem -subj \"/CN=client\"; openssl x509 -req -in client-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -days 365; curl --cacert ca.pem --cert client-cert.pem --key client-key.pem https://localhost:<port>/...")).addOption(new Option("--mtls-cert <path>", "PEM-encoded server certificate for mutual TLS. Self-signed is fine for local dev. Must be set together with --mtls-truststore + --mtls-key.")).addOption(new Option("--mtls-key <path>", "PEM-encoded server private key matching --mtls-cert. Must be set together with --mtls-truststore + --mtls-cert.")).addOption(new Option("--allow-unverified-sigv4", "Opt-in: allow AWS_IAM SigV4 requests that cannot be cryptographically verified (foreign access-key-id, OR no local AWS credentials configured) to pass through with a placeholder principalId. DEFAULT off — fail-closed so unauthenticated bypass is impossible against `event.requestContext.identity.accessKey`-trusting handler code. Use only in dev loops where you understand the risk.").default(false)).action(withErrorHandling(localStartApiCommand));
47956
+ const startApi = new Command("start-api").description("Run a long-running local HTTP server that maps API Gateway routes (REST v1, HTTP API, Function URL) to Lambda invocations against the AWS Lambda Runtime Interface Emulator (Docker required). Supports Lambda TOKEN/REQUEST authorizers, Cognito User Pool / HTTP v2 JWT authorizers, and AWS_IAM auth (REST v1 `AuthorizationType: AWS_IAM` and Function URL `AuthType: AWS_IAM` — SigV4 signature verification only; IAM policy evaluation is NOT emulated; see https://github.com/go-to-k/cdkd/blob/main/docs/local-emulation.md). When JWKS is unreachable, JWT authorizers fall back to pass-through (every token accepted) with a warn line — local dev fallback. VPC-config Lambdas run locally and surface a warn line at startup; their containers do NOT get attached to the deployed VPC subnets, so calls to private RDS / ElastiCache will fail.").argument("[target]", "Optional API filter. Accepts the bare CDK logical id ('MyHttpApi'; single-stack apps only), stack-qualified logical id ('MyStack:MyHttpApi'), full CDK Construct path ('MyStack/MyHttpApi/Resource'), or an ancestor Construct path that prefix-matches ('MyStack/MyHttpApi'). When omitted, every discovered API gets its own server. Mirrors `cdkd local invoke` / `cdkd local run-task` target syntax.").addOption(new Option("--port <port>", "HTTP server port (default: auto-allocate)").default("0")).addOption(new Option("--host <host>", "Bind address").default("127.0.0.1")).addOption(new Option("--stack <name>", "Stack to start (single-stack apps auto-detect)")).addOption(new Option("--warm", "Pre-start one container per Lambda at server boot").default(false)).addOption(new Option("--per-lambda-concurrency <n>", "Pool size cap per Lambda (default 2, max 4)").default("2")).addOption(new Option("--no-pull", "Skip docker pull (cached image)")).addOption(new Option("--container-host <host>", "IP the host uses to bind/probe the RIE port (must be a numeric IP — `docker run -p <ip>:<port>:8080` rejects hostnames). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--debug-port-base <port>", "Reserve a contiguous --debug-port range (one per Lambda)")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--assume-role <arn-or-pair>", "Assume the Lambda's execution role and forward STS-issued temp creds. Bare <arn> = global default; <LogicalId>=<arn> = per-Lambda override (repeatable). Per-Lambda > global > unset (developer creds passed through).").argParser((raw, prev) => parseAssumeRoleToken(raw, prev))).addOption(new Option("--watch", "Hot-reload: watch the CDK app source tree and re-synth + re-discover routes on a source edit (cdk.out / node_modules / .git excluded; honors cdk.json watch.include / watch.exclude). Off by default; the server keeps the previous version serving when synth fails mid-reload.").default(false)).addOption(new Option("--stage <name>", "Select an API Gateway Stage by its 'StageName'. Default: the first Stage attached to each API. Drives event.stageVariables for both REST v1 and HTTP API v2. NOTE: For HTTP API v2 routes, requestContext.stage is always '$default' regardless of this flag (AWS-side limitation — HTTP API only exposes one stage to the integration event); only event.stageVariables is affected for v2 routes. For REST v1 routes the selected StageName is also threaded into requestContext.stage.")).addOption(new Option("--api <id>", "DEPRECATED — use the positional <target> argument instead. Same accepted forms (bare logical id, stack-qualified, Construct path, ancestor prefix). Will be removed in a future major release.")).addOption(new Option("--layer-role-arn <arn>", "Role to sts:AssumeRole before calling lambda:GetLayerVersion on every literal-ARN entry in Properties.Layers (issue #448). Use only when the dev credentials cannot read the layer — typically cross-account layers. AWS-published public layers (e.g. Lambda Powertools) are readable from every account and need no role.")).addOption(new Option("--from-state", "Read cdkd S3 state for every routed stack and substitute Ref / Fn::GetAtt / Fn::Sub / Fn::Join (and AWS pseudo parameters) in Lambda env vars with the deployed physical IDs / attributes. Off by default — pre-PR warn-and-drop semantics are preserved. Turn on for stacks already deployed via cdkd deploy. Mirrors `cdkd local invoke --from-state` / `cdkd local run-task --from-state`. Re-runs against fresh state on every hot-reload firing (--watch).").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via DescribeStackResources and substitute Ref / Fn::ImportValue in Lambda env vars with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the cdkd stack name per routed stack; pass an explicit value when a single CFn stack should serve every routed stack. Mutually exclusive with --from-state. Fn::GetAtt is warn-and-dropped in v1 (CFn DescribeStackResources does not return per-attribute values).")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-state when the same stack name has state in multiple regions, and with --from-cfn-stack as the CFn client region (cdkd does not have a separate --cfn-stack-region flag).")).addOption(new Option("--mtls-truststore <path>", "PEM-encoded CA bundle for client-certificate verification (mutual TLS). When set, the local server switches from HTTP to HTTPS and the TLS handshake rejects clients whose certificate doesn't chain to one of these CAs. Verified certs are surfaced on the Lambda event under requestContext.identity.clientCert (REST v1) / requestContext.authentication.clientCert (HTTP API v2). Must be set together with --mtls-cert + --mtls-key; partial flag sets are rejected. Generate a CA + server + client cert for local dev: openssl req -x509 -newkey rsa:2048 -nodes -keyout ca-key.pem -out ca.pem -subj \"/CN=cdkd-local-ca\" -days 365; openssl req -newkey rsa:2048 -nodes -keyout server-key.pem -out server-csr.pem -subj \"/CN=localhost\"; openssl x509 -req -in server-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 365; openssl req -newkey rsa:2048 -nodes -keyout client-key.pem -out client-csr.pem -subj \"/CN=client\"; openssl x509 -req -in client-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -days 365; curl --cacert ca.pem --cert client-cert.pem --key client-key.pem https://localhost:<port>/...")).addOption(new Option("--mtls-cert <path>", "PEM-encoded server certificate for mutual TLS. Self-signed is fine for local dev. Must be set together with --mtls-truststore + --mtls-key.")).addOption(new Option("--mtls-key <path>", "PEM-encoded server private key matching --mtls-cert. Must be set together with --mtls-truststore + --mtls-cert.")).addOption(new Option("--strict-sigv4", "Opt-in: enforce strict AWS_IAM SigV4 verification. When set, requests whose signature cannot be cryptographically verified (foreign access-key-id, OR no local AWS credentials configured) are denied. DEFAULT off — warn-and-pass with a placeholder principalId, matching cdk-local's `cdkl start-api`. Enable this when you want local parity with the deployed API Gateway's signature enforcement.").default(false)).action(withErrorHandling(localStartApiCommand));
47791
47957
  [
47792
47958
  ...commonOptions,
47793
47959
  ...appOptions,
@@ -49760,174 +49926,6 @@ function createLocalInvokeAgentCoreCommand() {
49760
49926
  //#endregion
49761
49927
  //#region src/cli/commands/local-start-alb.ts
49762
49928
  /**
49763
- * Issue #86 v1 — parse `--lb-port <listenerPort>=<hostPort>` overrides into a
49764
- * `listenerPort -> hostPort` map. The local ALB front-door binds the listener
49765
- * port on the host by default, but a privileged listener port (e.g. 80 / 443)
49766
- * fails to bind as non-root on macOS, so the user opts in to a non-privileged
49767
- * host port (e.g. `--lb-port 80=8080`). Repeatable; each value is
49768
- * `<listenerPort>=<hostPort>` with both in 1-65535.
49769
- */
49770
- function parseLbPortOverrides(values) {
49771
- const out = {};
49772
- for (const raw of values ?? []) {
49773
- const m = /^(\d+)=(\d+)$/.exec(raw.trim());
49774
- if (!m) throw new LocalStartServiceError(`Invalid --lb-port '${raw}'. Expected <listenerPort>=<hostPort> (e.g. 80=8080).`);
49775
- const listenerPort = Number(m[1]);
49776
- const hostPort = Number(m[2]);
49777
- for (const [label, p] of [["listener", listenerPort], ["host", hostPort]]) if (p < 1 || p > 65535) throw new LocalStartServiceError(`Invalid --lb-port '${raw}': ${label} port must be 1-65535.`);
49778
- out[listenerPort] = hostPort;
49779
- }
49780
- return out;
49781
- }
49782
- /**
49783
- * Resolve an ALB target string (`Stack/Path` display path or `Stack:LogicalId`)
49784
- * to its stack + `AWS::ElasticLoadBalancingV2::LoadBalancer` logical id. Mirrors
49785
- * the ECS service resolver's target grammar.
49786
- */
49787
- function resolveAlbTarget(target, stacks) {
49788
- if (stacks.length === 0) throw new LocalStartServiceError("No stacks found in the synthesized assembly.");
49789
- const parsed = parseEcsTarget(target);
49790
- const stack = pickStack(parsed.stackPattern, stacks, target);
49791
- const resources = stack.template.Resources ?? {};
49792
- if (parsed.isPath) {
49793
- const index = buildCdkPathIndex(stack.template);
49794
- const albs = resolveCdkPathToLogicalIds(parsed.pathOrId, index).filter(({ logicalId }) => {
49795
- const r = resources[logicalId];
49796
- return r !== void 0 && isApplicationLoadBalancer(r);
49797
- });
49798
- if (albs.length === 0) throw notFound(target, stack, resources);
49799
- if (albs.length > 1) throw new LocalStartServiceError(`Target '${target}' matches ${albs.length} load balancers in ${stack.stackName}: ${albs.map((a) => a.logicalId).join(", ")}. Refine the path or use the stack:LogicalId form.`);
49800
- return {
49801
- stack,
49802
- albLogicalId: albs[0].logicalId
49803
- };
49804
- }
49805
- const res = resources[parsed.pathOrId];
49806
- if (!res || !isApplicationLoadBalancer(res)) throw notFound(target, stack, resources);
49807
- return {
49808
- stack,
49809
- albLogicalId: parsed.pathOrId
49810
- };
49811
- }
49812
- function pickStack(stackPattern, stacks, target) {
49813
- if (stackPattern === null) {
49814
- if (stacks.length === 1) return stacks[0];
49815
- throw new LocalStartServiceError(`Target '${target}' has no stack prefix, and the assembly contains ${stacks.length} stacks: ${stacks.map((s) => s.stackName).join(", ")}. Pass it as 'Stack/Path' or 'Stack:LogicalId'.`);
49816
- }
49817
- const matched = matchStacks(stacks, [stackPattern]);
49818
- if (matched.length === 0) throw new LocalStartServiceError(`No stack matches '${stackPattern}'. Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`);
49819
- if (matched.length > 1) throw new LocalStartServiceError(`Multiple stacks match '${stackPattern}': ${matched.map((s) => s.stackName).join(", ")}. Refine the pattern.`);
49820
- return matched[0];
49821
- }
49822
- function notFound(target, stack, resources) {
49823
- const albs = Object.entries(resources).filter(([, r]) => r.Type === "AWS::ElasticLoadBalancingV2::LoadBalancer").map(([logicalId]) => logicalId);
49824
- const available = albs.length > 0 ? ` Available load balancers in ${stack.stackName}: ${albs.join(", ")}.` : ` ${stack.stackName} declares no AWS::ElasticLoadBalancingV2::LoadBalancer resources.`;
49825
- return new LocalStartServiceError(`Target '${target}' did not match an application Load Balancer in ${stack.stackName}.${available}`);
49826
- }
49827
- /**
49828
- * `cdkl start-alb` strategy — name the ALB, boot the ECS service(s) behind it,
49829
- * and expose each listener via a local front-door. Mirrors how `start-api`
49830
- * names the API and serves its backing Lambdas.
49831
- */
49832
- function albStrategy(options) {
49833
- const lbPortOverrides = parseLbPortOverrides(options.lbPort);
49834
- return {
49835
- pickEntries: (stacks) => listTargets(stacks).loadBalancers,
49836
- pickerMessage: "Select one or more Application Load Balancers to run",
49837
- pickerNoun: "Application Load Balancers",
49838
- onMissing: () => new LocalStartServiceError(`${getEmbedConfig().cliName} start-alb requires at least one <target>. Pass one or more ALB paths like 'Stack/MyAlb', or run it in a TTY to pick interactively.`),
49839
- resolveBoots: (stacks, chosenTargets) => {
49840
- const warnings = [];
49841
- const serviceTargets = /* @__PURE__ */ new Set();
49842
- const listeners = [];
49843
- const claimedHostPorts = /* @__PURE__ */ new Map();
49844
- for (const albTarget of chosenTargets) {
49845
- const { stack, albLogicalId } = resolveAlbTarget(albTarget, stacks);
49846
- const resolution = resolveAlbFrontDoor(stack, albLogicalId);
49847
- warnings.push(...resolution.warnings);
49848
- const qualifyTarget = (t) => {
49849
- if (t.kind === "lambda") return {
49850
- kind: "lambda",
49851
- lambda: resolveLambdaTarget(`${stack.stackName}:${t.lambdaLogicalId}`, stacks),
49852
- targetGroupArn: `${stack.stackName}:${t.targetGroupLogicalId}`,
49853
- multiValueHeaders: t.multiValueHeaders,
49854
- weight: t.weight
49855
- };
49856
- const serviceTarget = `${stack.stackName}:${t.serviceLogicalId}`;
49857
- serviceTargets.add(serviceTarget);
49858
- return {
49859
- kind: "ecs",
49860
- serviceTarget,
49861
- targetContainerName: t.targetContainerName,
49862
- targetContainerPort: t.targetContainerPort,
49863
- weight: t.weight
49864
- };
49865
- };
49866
- const qualify = (action) => {
49867
- if (action.kind === "forward") return {
49868
- kind: "forward",
49869
- targets: action.targets.map(qualifyTarget)
49870
- };
49871
- if (action.kind === "redirect") return {
49872
- kind: "redirect",
49873
- statusCode: action.statusCode,
49874
- ...action.protocol !== void 0 && { protocol: action.protocol },
49875
- ...action.host !== void 0 && { host: action.host },
49876
- ...action.port !== void 0 && { port: action.port },
49877
- ...action.path !== void 0 && { path: action.path },
49878
- ...action.query !== void 0 && { query: action.query }
49879
- };
49880
- return {
49881
- kind: "fixed-response",
49882
- statusCode: action.statusCode,
49883
- ...action.contentType !== void 0 && { contentType: action.contentType },
49884
- ...action.messageBody !== void 0 && { messageBody: action.messageBody }
49885
- };
49886
- };
49887
- for (const listener of resolution.listeners) {
49888
- const hostPort = lbPortOverrides[listener.listenerPort] ?? listener.listenerPort;
49889
- const claimedBy = claimedHostPorts.get(hostPort);
49890
- if (claimedBy !== void 0) {
49891
- warnings.push(`Listener port ${listener.listenerPort} would bind host port ${hostPort}, already claimed by listener port ${claimedBy}; the local front-door fronts only the first. Use --lb-port to remap one of them.`);
49892
- continue;
49893
- }
49894
- claimedHostPorts.set(hostPort, listener.listenerPort);
49895
- listeners.push({
49896
- listenerPort: listener.listenerPort,
49897
- hostPort,
49898
- protocol: listener.listenerProtocol,
49899
- ...listener.defaultAction ? { defaultAction: qualify(listener.defaultAction) } : {},
49900
- ...listener.defaultAuthGuard ? { defaultAuthGuard: listener.defaultAuthGuard } : {},
49901
- rules: listener.rules.map((r) => ({
49902
- priority: r.priority,
49903
- pathPatterns: r.pathPatterns,
49904
- hostPatterns: r.hostPatterns,
49905
- httpHeaderConditions: r.httpHeaderConditions,
49906
- httpRequestMethods: r.httpRequestMethods,
49907
- queryStringConditions: r.queryStringConditions,
49908
- sourceIpCidrs: r.sourceIpCidrs,
49909
- action: qualify(r.action),
49910
- ...r.authGuard ? { authGuard: r.authGuard } : {}
49911
- }))
49912
- });
49913
- }
49914
- }
49915
- const boots = [...serviceTargets].map((target) => ({ target }));
49916
- const resolvedPorts = new Set(listeners.map((l) => l.listenerPort));
49917
- for (const portStr of Object.keys(lbPortOverrides)) {
49918
- const port = Number(portStr);
49919
- if (!resolvedPorts.has(port)) warnings.push(`--lb-port override for listener port ${port} matched no ALB listener resolved for the named target(s); it was ignored.`);
49920
- }
49921
- return {
49922
- boots,
49923
- ...listeners.length > 0 ? { frontDoor: { listeners } } : {},
49924
- warnings
49925
- };
49926
- },
49927
- lbPortOverrides
49928
- };
49929
- }
49930
- /**
49931
49929
  * `cdkl start-alb <Stack/Alb>` — Issue #86 v1. Names an
49932
49930
  * `AWS::ElasticLoadBalancingV2::LoadBalancer`, discovers the ECS service(s)
49933
49931
  * behind its HTTP `forward` listeners, boots their replicas, and stands up a
@@ -49935,9 +49933,11 @@ function albStrategy(options) {
49935
49933
  * The symmetric ALB counterpart of `start-api`.
49936
49934
  */
49937
49935
  function createLocalStartAlbCommand() {
49938
- return addCommonEcsServiceOptions(new Command("start-alb").description("Run an Application Load Balancer locally: name the ALB, and cdk-local boots the ECS service(s) behind its listeners and stands up a local front-door on each listener port that round-robins across the running replicas and routes its listener rules across the backing services — a stable host endpoint, like behind a real load balancer. The symmetric ALB counterpart of `start-api`. Each <target> accepts a CDK display path (MyStack/MyAlb) or stack-qualified logical ID; single-stack apps may omit the stack prefix. Supports HTTP and HTTPS listeners (TLS terminated locally with --tls-cert/--tls-key or an auto-generated self-signed cert); all six ALB rule-condition fields (path-pattern / host-header / http-header / http-request-method / query-string / source-ip); forward (single and weighted), redirect, and fixed-response actions; and ECS or Lambda targets (a Lambda target group is invoked locally via the Lambda RIE). authenticate-cognito / authenticate-oidc actions enforce a local Bearer-JWT check (or AWSELBAuthSessionCookie pass-through) against the same JWKS / OIDC discovery URL the deployed ALB would; use --bearer-token <jwt> to inject a default token or --no-verify-auth to disable the guard. Omit <targets> in an interactive terminal to multi-select the load balancers from a list.").argument("[targets...]", "One or more CDK display paths or stack-qualified logical IDs of the AWS::ElasticLoadBalancingV2::LoadBalancer resources to run (omit to multi-select interactively in a TTY)").addOption(new Option("--lb-port <listenerPort=hostPort...>", "Bind the local front-door on a specific host port (e.g. 80=8080); repeatable. Default: host port == ALB listener port. Use this on macOS to remap a privileged listener port (< 1024) to a non-privileged host port.")).addOption(new Option("--tls-cert <path>", "PEM-encoded server certificate for HTTPS front-door listeners. Must be set together with --tls-key. Omit both flags to auto-generate a self-signed cert (cached under $XDG_CACHE_HOME/cdk-local/alb-https/, default ~/.cache/cdk-local/alb-https/); requires openssl on PATH. The deployed Listener Certificates[] are NOT fetched (ACM private keys are not retrievable by design). The auto-generated cert lists DNS:localhost,IP:127.0.0.1 as SubjectAltName, so a client validating a non-loopback --container-host will fail the SAN check — pass --tls-cert / --tls-key with a SAN covering that host instead.")).addOption(new Option("--tls-key <path>", "PEM-encoded server private key matching --tls-cert. Must be set together with --tls-cert.")).addOption(new Option("--no-verify-auth", "Disable local enforcement of authenticate-cognito / authenticate-oidc actions. Every request is served as if the auth check passed. Useful for local dev where you do not want to mint a Bearer token at all.")).addOption(new Option("--bearer-token <jwt>", "Default Bearer JWT injected as Authorization: Bearer <jwt> when the inbound request has none. Verified against the same JWKS / OIDC discovery URL the deployed ALB would (signature + iss + aud + exp). Local-dev convenience; cookie pass-through (AWSELBAuthSessionCookie-*) also works.")).addOption(new Option("--from-state", "Read cdkd's S3 state for the target stack and substitute Ref / Fn::GetAtt / Fn::Sub / Fn::ImportValue / Fn::GetStackOutput intrinsics in container images, environment variables, secrets, role ARNs, and volumes of the ECS services behind the ALB. Mutually exclusive with --from-cfn-stack.").default(false)).addOption(new Option("--state-bucket <bucket>", "S3 bucket for --from-state. Falls back to CDKD_STATE_BUCKET env or cdk.json context.cdkd.stateBucket.")).addOption(new Option("--state-prefix <prefix>", "S3 key prefix for --from-state state files.").default("cdkd")).action(withErrorHandling(async (targets, options) => {
49936
+ const cmd = new Command("start-alb").description("Run an Application Load Balancer locally: name the ALB, and cdk-local boots the ECS service(s) behind its listeners and stands up a local front-door on each listener port that round-robins across the running replicas and routes its listener rules across the backing services — a stable host endpoint, like behind a real load balancer. The symmetric ALB counterpart of `start-api`. Each <target> accepts a CDK display path (MyStack/MyAlb) or stack-qualified logical ID; single-stack apps may omit the stack prefix. Supports HTTP and HTTPS listeners by default a cloud-HTTPS listener is served over plain HTTP locally (with X-Forwarded-Proto: https preserved). Pass --tls (or --tls-cert / --tls-key) to terminate TLS locally with a self-signed or user-supplied cert. All six ALB rule-condition fields are honored (path-pattern / host-header / http-header / http-request-method / query-string / source-ip); forward (single and weighted), redirect, and fixed-response actions; and ECS or Lambda targets (a Lambda target group is invoked locally via the Lambda RIE). authenticate-cognito / authenticate-oidc actions enforce a local Bearer-JWT check (or AWSELBAuthSessionCookie pass-through) against the same JWKS / OIDC discovery URL the deployed ALB would; use --bearer-token <jwt> to inject a default token or --no-verify-auth to disable the guard. Omit <targets> in an interactive terminal to multi-select the load balancers from a list.").argument("[targets...]", "One or more CDK display paths or stack-qualified logical IDs of the AWS::ElasticLoadBalancingV2::LoadBalancer resources to run (omit to multi-select interactively in a TTY)").addOption(new Option("--from-state", "Read cdkd's S3 state for the target stack and substitute Ref / Fn::GetAtt / Fn::Sub / Fn::ImportValue / Fn::GetStackOutput intrinsics in container images, environment variables, secrets, role ARNs, and volumes of the ECS services behind the ALB. Mutually exclusive with --from-cfn-stack.").default(false)).addOption(new Option("--state-bucket <bucket>", "S3 bucket for --from-state. Falls back to CDKD_STATE_BUCKET env or cdk.json context.cdkd.stateBucket.")).addOption(new Option("--state-prefix <prefix>", "S3 key prefix for --from-state state files.").default("cdkd")).action(withErrorHandling(async (targets, options) => {
49939
49937
  await runEcsServiceEmulator(targets, options, albStrategy(options), cdkdExtraStateProviders);
49940
- })));
49938
+ }));
49939
+ addAlbSpecificOptions(cmd);
49940
+ return addCommonEcsServiceOptions(cmd);
49941
49941
  }
49942
49942
 
49943
49943
  //#endregion
@@ -49958,8 +49958,8 @@ const CDKD_EMBED_CONFIG = {
49958
49958
  resourceNamePrefix: "cdkd-local",
49959
49959
  awsBindMountPath: "/cdkd-aws",
49960
49960
  envPrefix: "CDKD",
49961
- sigV4StrictByDefault: true,
49962
- sigV4OptFlag: "--allow-unverified-sigv4"
49961
+ sigV4StrictByDefault: false,
49962
+ sigV4OptFlag: "--strict-sigv4"
49963
49963
  };
49964
49964
  /**
49965
49965
  * `cdkd local invoke <target>` — run a Lambda function locally inside a
@@ -51856,7 +51856,7 @@ function reorderArgs(argv) {
51856
51856
  */
51857
51857
  async function main() {
51858
51858
  const program = new Command();
51859
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.199.0");
51859
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.201.0");
51860
51860
  program.addCommand(createBootstrapCommand());
51861
51861
  program.addCommand(createSynthCommand());
51862
51862
  program.addCommand(createListCommand());