@go-to-k/cdkd 0.284.19 → 0.284.21

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,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { $ as isTerminationProtectionPropagationError, $n as ResourceTimeoutError, $t as readBootstrapMarkerBody, A as renderStatefulReason, An as canonicalizeRegion, At as INTRINSIC_KEYS, B as exportAliasCollisionScrubWarning, Bt as stringifyValue, C as isFinalSnapshotError, Cn as CFN_TEMPLATE_BODY_LIMIT, Ct as s3BucketArn, D as extractDeploymentEventError, Dn as uploadCfnTemplate, Dt as s3BucketWebsiteUrl, E as makeCanonicalizePropertiesFn, En as findLargeInlineResources, Et as s3BucketRegionalDomainName, F as green, Fn as resolveBucketRegion, Ft as LockManager, G as collectInlinePolicyNamesManagedBySiblings, Gn as LocalInvokeBuildError$1, Gt as rewriteTemplateAssetReferences, H as secretBearingStateKeyWarning, Ht as buildAssetRedirectMap, I as red, In as AwsClients, It as S3StateBackend, J as findActionableSilentDrops, Jt as AssetModeResolver, K as clearOnUpdateRemoval, Kn as LocalMigrateError, Kt as escapeRegExp, L as yellow, Ln as getAwsClients, Lt as rebuildClientForBucketRegion, M as bold, Mn as AssemblyReader, Mt as withRetry, N as cyan, Nn as processStackMessages, Nt as DagBuilder, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as expectedOwnerParam, Ot as applyRoleArnIfSet, P as gray, Pt as TemplateParser, Q as disableInstanceApiTermination, Qn as ProvisioningError, Qt as parseBootstrapMarker, R as collectDeclaredOutputNames, Rn as resetAwsClients, Rt as shouldRetainResource, S as createPreDeleteFinalSnapshot, Sn as warnDeprecatedNoPrefixCliFlag, St as scrubResourceRecord, T as unsupportedFinalSnapshotError, Tn as MIGRATE_TMP_PREFIX, Tt as s3BucketDualStackDomainName, U as stateKeySecretExposure, Ut as createAssetRedirectResolver, V as isExportAliasCollision, Vn as CdkdError, Vt as WorkGraph, W as IAMRoleProvider, Wn as DeployCancelledError, Wt as loadPublishableAssetManifest, X as CloudControlProvider, Xn as NestedStackChildDirectDestroyError, Xt as ensureAssetStorage, Y as findSilentDropProperties, Yn as MissingCdkCliError, Yt as BOOTSTRAP_MARKER_PREFIX, Z as slowCcOperationTimeoutMs, Zn as PartialFailureError, Zt as getBootstrapMarkerKey, _ as computeImplicitDeleteEdges, _n as resolveSkipPrefix, _t as STATE_SOURCED_READBACK_RULES, a as DeploymentEventsStore, an as getDockerCmd, at as normalizeAwsTagsToCfn, b as buildFinalSnapshotIdentifier, bn as resolveUseCdkBootstrapAssets, bt as maskSecretsInText, c as replayFailedOperations, cn as AssetManifestLoader, cr as withErrorHandling, ct as coerceCfnBoolean, d as updatePartialReason, dn as synthesisStatusMessage, dr as isThrottlingError, dt as readConfigString, en as validateAssetBucketName, er as ResourceUpdateNotSupportedError, et as IntrinsicFunctionResolver, f as UNSPECIFIED_SKIP_REASON, fn as getDefaultStateBucketName, fr as markNonRetryable, ft as replayWarn, g as IMPLICIT_DELETE_DEPENDENCIES, gn as resolveCaptureObservedState, gt as STATE_SOURCED_CROSS_GENERATION_RULES, h as maskingRetryLogger, hn as resolveAutoAssetStorage, ht as requireConfigString, i as DeploymentEventsReader, in as formatDockerLoginError, it as WAFv2WebACLProvider, j as formatResourceLine, jn as derivePartitionAndUrlSuffix, jt as describeTypeWithThrottleRetry, k as isStatefulRecreateTargetSync, kn as PARTITION_TABLE, kt as DiffCalculator, l as replayRollback, ln as getDockerImageBySourceHash, lr as isMarkedNonRetryable, lt as configBooleanRefusal, m as withResourceDeadline, mn as resolveApp, mt as requireConfigObject, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as buildDenyExternalAccessPolicy, nr as StackTerminationProtectionError, nt as getAccountInfo, o as planFailedOps, on as runDockerForeground, ot as resolveExplicitPhysicalId, p as deleteSkipReason, pn as getLegacyStateBucketName, pt as requireConfigArray, q as ProviderRegistry, qn as LocalStartServiceError, qt as stripControlChars, r as DeployEngine, rn as buildDockerImage, rt as refStateLookupFromResource, s as planRollback, sn as runDockerStreaming, sr as normalizeAwsError, st as assertRegionMatch, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as validateContainerRepoName, tr as StackHasActiveImportsError, tt as cfnRefValueFromPhysicalId, u as updatePartialMessage, un as Synthesizer, ur as isRetryableTransientError, ut as configStringRefusal, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as resolveStateBucketWithDefault, vt as TEMPLATE_SOURCED_RULES, w as refusesFinalSnapshot, wn as CFN_TEMPLATE_URL_LIMIT, wt as s3BucketDomainName, x as ccRoutedFinalSnapshotError, xn as stateBucketExistenceConfirmed, xt as redactSecretsForState, y as PRE_DELETE_SNAPSHOT_TYPES, yn as resolveStateBucketWithDefaultAndSource, yt as createSecretMasker, z as collectPublishedOutputNames, zn as setAwsClients, zt as AssetPublisher } from "./deploy-engine-Bh_LPdju.js";
2
+ import { $ as isTerminationProtectionPropagationError, $n as PartialFailureError, $t as getBootstrapMarkerKey, A as renderStatefulReason, An as expectedOwnerParam, At as applyRoleArnIfSet, B as exportAliasCollisionScrubWarning, Bn as resetAwsClients, Bt as shouldRetainResource, C as isFinalSnapshotError, Cn as stateBucketExistenceConfirmed, Ct as redactSecretsForState, D as extractDeploymentEventError, Dn as MIGRATE_TMP_PREFIX, Dt as s3BucketDualStackDomainName, E as makeCanonicalizePropertiesFn, En as CFN_TEMPLATE_URL_LIMIT, Et as s3BucketDomainName, F as green, Fn as processStackMessages, Ft as DagBuilder, G as collectInlinePolicyNamesManagedBySiblings, Gt as createAssetRedirectResolver, H as secretBearingStateKeyWarning, Ht as stringifyValue, I as red, It as TemplateParser, J as findActionableSilentDrops, Jn as LocalMigrateError, Jt as escapeRegExp, K as clearOnUpdateRemoval, Kn as DeployCancelledError, Kt as loadPublishableAssetManifest, L as yellow, Ln as resolveBucketRegion, Lt as LockManager, M as bold, Mn as canonicalizeRegion, Mt as INTRINSIC_KEYS, N as cyan, Nn as derivePartitionAndUrlSuffix, Nt as describeTypeWithThrottleRetry, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as findLargeInlineResources, Ot as s3BucketRegionalDomainName, P as gray, Pn as AssemblyReader, Pt as withRetry, Q as disableInstanceApiTermination, Qn as NestedStackChildDirectDestroyError, Qt as ensureAssetStorage, R as collectDeclaredOutputNames, Rn as AwsClients, Rt as S3StateBackend, S as createPreDeleteFinalSnapshot, Sn as resolveUseCdkBootstrapAssets, St as maskSecretsInText, T as unsupportedFinalSnapshotError, Tn as CFN_TEMPLATE_BODY_LIMIT, Tt as s3BucketArn, U as stateKeySecretExposure, Un as CdkdError, Ut as WorkGraph, V as isExportAliasCollision, Vn as setAwsClients, Vt as AssetPublisher, W as IAMRoleProvider, Wt as buildAssetRedirectMap, X as CloudControlProvider, Xt as AssetModeResolver, Y as findSilentDropProperties, Yn as LocalStartServiceError, Yt as stripControlChars, Z as slowCcOperationTimeoutMs, Zn as MissingCdkCliError, Zt as BOOTSTRAP_MARKER_PREFIX, _ as computeImplicitDeleteEdges, _n as resolveAutoAssetStorage, _t as STATE_SOURCED_READBACK_RULES, a as DeploymentEventsStore, an as buildDockerImage, at as normalizeAwsTagsToCfn, b as buildFinalSnapshotIdentifier, bn as resolveStateBucketWithDefault, bt as dynamicReferenceTokens, c as replayFailedOperations, cn as runDockerForeground, ct as coerceCfnBoolean, d as updatePartialReason, dn as getDockerImageBySourceHash, dr as isMarkedNonRetryable, dt as readConfigString, en as parseBootstrapMarker, er as ProvisioningError, et as IntrinsicFunctionResolver, f as UNSPECIFIED_SKIP_REASON, fn as Synthesizer, fr as isRetryableTransientError, ft as replayWarn, g as IMPLICIT_DELETE_DEPENDENCIES, gn as resolveApp, gt as STATE_SOURCED_CROSS_GENERATION_RULES, h as maskingRetryLogger, hn as getLegacyStateBucketName, ht as requireConfigString, i as DeploymentEventsReader, in as buildDenyExternalAccessPolicy, ir as StackTerminationProtectionError, it as WAFv2WebACLProvider, j as formatResourceLine, jn as PARTITION_TABLE, jt as DiffCalculator, k as isStatefulRecreateTargetSync, kn as uploadCfnTemplate, kt as s3BucketWebsiteUrl, l as replayRollback, ln as runDockerStreaming, lr as normalizeAwsError, lt as configBooleanRefusal, m as withResourceDeadline, mn as getDefaultStateBucketName, mr as markNonRetryable, mt as requireConfigObject, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as validateAssetBucketName, nr as ResourceUpdateNotSupportedError, nt as getAccountInfo, o as planFailedOps, on as formatDockerLoginError, ot as resolveExplicitPhysicalId, p as deleteSkipReason, pn as synthesisStatusMessage, pr as isThrottlingError, pt as requireConfigArray, q as ProviderRegistry, qn as LocalInvokeBuildError$1, qt as rewriteTemplateAssetReferences, r as DeployEngine, rn as validateContainerRepoName, rr as StackHasActiveImportsError, rt as refStateLookupFromResource, s as planRollback, sn as getDockerCmd, st as assertRegionMatch, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as readBootstrapMarkerBody, tr as ResourceTimeoutError, tt as cfnRefValueFromPhysicalId, u as updatePartialMessage, un as AssetManifestLoader, ur as withErrorHandling, ut as configStringRefusal, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as resolveCaptureObservedState, vt as TEMPLATE_SOURCED_RULES, w as refusesFinalSnapshot, wn as warnDeprecatedNoPrefixCliFlag, wt as scrubResourceRecord, x as ccRoutedFinalSnapshotError, xn as resolveStateBucketWithDefaultAndSource, xt as isSingleDynamicReferenceToken, y as PRE_DELETE_SNAPSHOT_TYPES, yn as resolveSkipPrefix, yt as createSecretMasker, z as collectPublishedOutputNames, zn as getAwsClients, zt as rebuildClientForBucketRegion } from "./deploy-engine--N_xnvjI.js";
3
3
  import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, f as getCurrentStackName, h as withStackName, i as runStackBuffered, m as withSkipPrefix, n as getLogger, o as PATTERN_B_NAME_OPTIONS, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-zRrlbaQt.js";
4
- import { t as ASGProvider } from "./asg-provider-CuSoYdf6.js";
4
+ import { t as ASGProvider } from "./asg-provider-D5G53CUp.js";
5
5
  import { AsyncLocalStorage } from "node:async_hooks";
6
6
  import { createHash, randomBytes, randomUUID } from "node:crypto";
7
7
  import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketEncryptionCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketOwnershipControlsCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, 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";
@@ -112,7 +112,11 @@ const commonOptions = [
112
112
  * Kept (rather than fully removed) so that scripts or muscle memory passing
113
113
  * `--region` do not break. The value IS still honored — every non-bootstrap
114
114
  * command consumes `options.region` as the highest-precedence region source
115
- * (`options.region || AWS_REGION || 'us-east-1'`): it sets the region of the
115
+ * (`--region` -> `AWS_REGION` -> `us-east-1`, except `cdkd gc` and `cdkd
116
+ * bootstrap --destroy`, which resolve the profile region and REFUSE rather
117
+ * than falling back to a literal, because they delete — issue #2029; the
118
+ * value is folded to its canonical spelling at each handler's entry, issue
119
+ * #2065): it sets the region of the
116
120
  * provisioning / state-bucket SDK clients, the `applyRoleArnIfSet` STS hop,
117
121
  * and (for `deploy` / `destroy` / `import` / `export` / `orphan`) the
118
122
  * `AWS_REGION` env var inherited by the CDK synth subprocess. The flag is
@@ -776,6 +780,92 @@ const destroyOptions = [
776
780
  */
777
781
  const skipFinalSnapshotOption = new Option("--skip-final-snapshot", "Delete resources whose DeletionPolicy (or, on replacements, UpdateReplacePolicy) is 'Snapshot' WITHOUT creating the final snapshot the policy promises (DATA LOSS — explicit opt-out of CloudFormation parity). By default cdkd creates the final snapshot for every CloudFormation-supported type (RDS DBInstance / DBCluster, Neptune / DocDB clusters, ElastiCache CacheCluster / ReplicationGroup, Redshift Cluster, EC2 Volume) and refuses the delete when it cannot — e.g. a resource managed via the Cloud Control API route, whose delete has no final-snapshot parameter. On 'cdkd rollback' the flag governs the rolled-back CREATE's DeletionPolicy; a reverse-replacement delete of the NEW resource keeps its own bounded UpdateReplacePolicy behavior and is unaffected.").default(false);
778
782
 
783
+ //#endregion
784
+ //#region src/cli/region-options.ts
785
+ /**
786
+ * ONE region-normalization point for the CLI's command handlers.
787
+ *
788
+ * Issue [#1795](https://github.com/go-to-k/cdkd/issues/1795) folded `--region`
789
+ * at the boundary of the four `cdkd local *` commands, for a reason that was
790
+ * never specific to them: AWS SDK endpoint resolution, SigV4's credential
791
+ * scope, this repo's own `PARTITION_TABLE` prefix walk and every ARN segment
792
+ * built from the value are ALL case-sensitive, so a raw spelling is wrong at
793
+ * each of them. Issue
794
+ * [#2065](https://github.com/go-to-k/cdkd/issues/2065) measured what that costs
795
+ * the commands the fix skipped — `cdkd deploy --region US-EAST-1` dies at the
796
+ * state-bucket preflight before it does anything:
797
+ *
798
+ * ```text
799
+ * FAIL S3 ListObjectsV2 region=US-EAST-1 AuthorizationHeaderMalformed:
800
+ * the region 'US-EAST-1' is wrong; expecting 'us-east-1'
801
+ * ```
802
+ *
803
+ * The fold lives here rather than at each consumer because a command reads
804
+ * `options.region` in many places — `applyRoleArnIfSet`, the
805
+ * `process.env.AWS_REGION` copy, several `new AwsClients({ region })` spreads —
806
+ * and folding per consumer is the shape that has needed a follow-up every time
807
+ * a new consumer was added. Double-folding is a no-op, which is what makes a
808
+ * second fold further down (`derivePartitionAndUrlSuffix`, `AwsClients`) safe
809
+ * rather than redundant.
810
+ */
811
+ /**
812
+ * Fold `--region` to its canonical spelling in place, at the handler's entry.
813
+ *
814
+ * Call this BEFORE the first consumer of `options.region` — in practice before
815
+ * `applyRoleArnIfSet`, which is the first AWS-touching call in most handlers.
816
+ *
817
+ * A handler that ALSO needs the user's exact spelling must capture it first.
818
+ * The one consumer with that need is the bootstrap-marker read, whose second
819
+ * probe exists precisely to find a key an unfolded `cdkd bootstrap` wrote
820
+ * (see `readBootstrapMarkerBody` in `src/assets/asset-storage.ts`); the
821
+ * `--stack-region` fold in `local-invoke.ts` keeps a `rawStackRegion` for the
822
+ * same class of reason.
823
+ */
824
+ function foldRegionOption(options) {
825
+ if (options.region !== void 0) options.region = canonicalizeRegion(options.region);
826
+ for (const key of ["AWS_REGION", "AWS_DEFAULT_REGION"]) {
827
+ const value = process.env[key];
828
+ if (value) process.env[key] = canonicalizeRegion(value);
829
+ }
830
+ }
831
+ /**
832
+ * The region the user NAMED — `--region` first, then `AWS_REGION` — canonical,
833
+ * or `undefined` when they named NEITHER.
834
+ *
835
+ * `undefined` is a distinct answer from any region string, and the distinction
836
+ * is the whole point of this helper. Issue
837
+ * [#2029](https://github.com/go-to-k/cdkd/issues/2029): materializing a
838
+ * `|| 'us-east-1'` literal collapses "the user named no region" into "the user
839
+ * named us-east-1", and the literal then wins over the profile region the AWS
840
+ * SDK's own chain would have resolved from `~/.aws/config`. For `cdkd gc` that
841
+ * meant evaluating — and deleting in — a region the user never mentioned, with
842
+ * no error, because `us-east-1` is a perfectly valid region.
843
+ *
844
+ * So a caller that builds an SDK client spreads this CONDITIONALLY
845
+ * (`...(named !== undefined && { region: named })`) and lets an absent region
846
+ * stay absent. A caller that needs a region as a VALUE — a state key, a bucket
847
+ * name, a marker key — must resolve one rather than pin a literal, and must
848
+ * resolve the SAME one its clients use or the two silently disagree.
849
+ *
850
+ * Deliberately reads `AWS_REGION` only, not `AWS_DEFAULT_REGION`: that is the
851
+ * precedence every one of these handlers already had, and widening it is a
852
+ * behavior change with no defect behind it.
853
+ */
854
+ function namedCliRegion(optionRegion) {
855
+ return canonicalizeRegion(optionRegion || process.env["AWS_REGION"]) || void 0;
856
+ }
857
+ /**
858
+ * The user's EXACT `--region` / `AWS_REGION` spelling, unfolded.
859
+ *
860
+ * Only the bootstrap-marker read wants this — it probes the canonical key
861
+ * first and this spelling second, so that a marker written by a pre-fold
862
+ * `cdkd bootstrap` is still found. Every other consumer wants
863
+ * {@link namedCliRegion}.
864
+ */
865
+ function rawCliRegion(optionRegion) {
866
+ return optionRegion || process.env["AWS_REGION"] || void 0;
867
+ }
868
+
779
869
  //#endregion
780
870
  //#region src/cli/commands/state-file-keys.ts
781
871
  /**
@@ -1067,13 +1157,12 @@ async function bootstrapDestroyCommand(options) {
1067
1157
  logger.debug("Options:", options);
1068
1158
  await applyRoleArnIfSet({
1069
1159
  roleArn: options.roleArn,
1070
- region: canonicalizeRegion(options.region)
1160
+ region: namedCliRegion(options.region)
1071
1161
  });
1072
- const rawRegion = options.region || process.env["AWS_REGION"] || "us-east-1";
1073
- const region = canonicalizeRegion(rawRegion);
1074
- const clientRegion = canonicalizeRegion(options.region || process.env["AWS_REGION"]) || void 0;
1162
+ const region = namedCliRegion(options.region) ?? "us-east-1";
1163
+ const rawRegion = rawCliRegion(options.region) ?? region;
1075
1164
  const awsClients = new AwsClients({
1076
- ...clientRegion !== void 0 && { region: clientRegion },
1165
+ region,
1077
1166
  ...options.profile && { profile: options.profile }
1078
1167
  });
1079
1168
  setAwsClients(awsClients);
@@ -1202,6 +1291,8 @@ async function bootstrapCommand(options) {
1202
1291
  if (options.verbose) logger.setLevel("debug");
1203
1292
  logger.info("Starting cdkd bootstrap...");
1204
1293
  logger.debug("Options:", options);
1294
+ const rawRegion = rawCliRegion(options.region) ?? "us-east-1";
1295
+ foldRegionOption(options);
1205
1296
  await applyRoleArnIfSet({
1206
1297
  roleArn: options.roleArn,
1207
1298
  region: options.region
@@ -1212,7 +1303,7 @@ async function bootstrapCommand(options) {
1212
1303
  });
1213
1304
  setAwsClients(awsClients);
1214
1305
  const s3Client = awsClients.s3;
1215
- const region = options.region || process.env["AWS_REGION"] || "us-east-1";
1306
+ const region = namedCliRegion(options.region) ?? "us-east-1";
1216
1307
  let bucketName;
1217
1308
  let accountId;
1218
1309
  if (options.stateBucket) {
@@ -1310,7 +1401,7 @@ async function bootstrapCommand(options) {
1310
1401
  ecrClient,
1311
1402
  stateBackend,
1312
1403
  accountId,
1313
- region,
1404
+ region: rawRegion,
1314
1405
  force: options.force,
1315
1406
  ...options.assetBucket && { assetBucketName: options.assetBucket },
1316
1407
  ...options.containerRepo && { containerRepoName: options.containerRepo }
@@ -1340,7 +1431,7 @@ async function bootstrapCommand(options) {
1340
1431
  * Create bootstrap command
1341
1432
  */
1342
1433
  function createBootstrapCommand() {
1343
- const cmd = new Command("bootstrap").description("Bootstrap cdkd by creating the S3 state bucket plus cdkd-owned asset storage (asset bucket + container-asset ECR repo) for the region. With --destroy, tear down the region's asset storage instead (issue #1010).").option("--state-bucket <bucket>", "Name of S3 bucket to create for state storage (default: cdkd-state-{accountId})").option("--force", "Create: force reconfiguration of an existing bucket. Destroy: delete the asset storage even when deployed stacks still reference it", false).option("--no-assets", "Skip cdkd asset storage (asset bucket / ECR repo / marker) — deploys keep publishing assets to the CDK bootstrap bucket/repo").option("--asset-bucket <name>", "Custom name for the cdkd asset bucket (default: cdkd-assets-{accountId}-{region}). Written into the bootstrap marker; re-bootstrapping a region with a different name is refused").option("--container-repo <name>", "Custom name for the container-asset ECR repository (default: cdkd-container-assets-{accountId}-{region}). Written into the bootstrap marker; re-bootstrapping a region with a different name is refused").option("--destroy", "Tear down the region's cdkd asset storage (empty + delete the asset bucket, force-delete the container-asset ECR repo, then delete the bootstrap marker last). The state bucket is kept unless --include-state-bucket is also passed", false).option("--include-state-bucket", "With --destroy: also delete the S3 state bucket. Refused while any stack state exists or any other region is still opted in to asset storage", false).addOption(new Option("--region <region>", "AWS region in which to create the state bucket (defaults to AWS_REGION env or us-east-1)")).action(withErrorHandling(async (options) => {
1434
+ const cmd = new Command("bootstrap").description("Bootstrap cdkd by creating the S3 state bucket plus cdkd-owned asset storage (asset bucket + container-asset ECR repo) for the region. With --destroy, tear down the region's asset storage instead (issue #1010).").option("--state-bucket <bucket>", "Name of S3 bucket to create for state storage (default: cdkd-state-{accountId})").option("--force", "Create: force reconfiguration of an existing bucket. Destroy: delete the asset storage even when deployed stacks still reference it", false).option("--no-assets", "Skip cdkd asset storage (asset bucket / ECR repo / marker) — deploys keep publishing assets to the CDK bootstrap bucket/repo").option("--asset-bucket <name>", "Custom name for the cdkd asset bucket (default: cdkd-assets-{accountId}-{region}). Written into the bootstrap marker; re-bootstrapping a region with a different name is refused").option("--container-repo <name>", "Custom name for the container-asset ECR repository (default: cdkd-container-assets-{accountId}-{region}). Written into the bootstrap marker; re-bootstrapping a region with a different name is refused").option("--destroy", "Tear down the region's cdkd asset storage (empty + delete the asset bucket, force-delete the container-asset ECR repo, then delete the bootstrap marker last). The state bucket is kept unless --include-state-bucket is also passed", false).option("--include-state-bucket", "With --destroy: also delete the S3 state bucket. Refused while any stack state exists or any other region is still opted in to asset storage", false).addOption(new Option("--region <region>", "AWS region to bootstrap, or to tear down with --destroy (defaults to AWS_REGION env, else us-east-1)")).action(withErrorHandling(async (options) => {
1344
1435
  if (options.destroy) {
1345
1436
  if (options.assetBucket !== void 0 || options.containerRepo !== void 0) throw new CdkdError("--asset-bucket / --container-repo cannot be combined with --destroy. The teardown reads the region's asset storage names from the bootstrap marker.", "INVALID_OPTIONS");
1346
1437
  if (!options.assets) throw new CdkdError("--no-assets cannot be combined with --destroy. The teardown scope is the asset storage itself; use --include-state-bucket to also delete the state bucket.", "INVALID_OPTIONS");
@@ -1431,6 +1522,7 @@ async function synthCommand(options) {
1431
1522
  const logger = getLogger();
1432
1523
  if (options.verbose) logger.setLevel("debug");
1433
1524
  warnIfDeprecatedRegion(options);
1525
+ foldRegionOption(options);
1434
1526
  await applyRoleArnIfSet({
1435
1527
  roleArn: options.roleArn,
1436
1528
  region: options.region
@@ -1568,6 +1660,7 @@ async function listCommand(patterns, options) {
1568
1660
  const logger = getLogger();
1569
1661
  if (options.verbose) logger.setLevel("debug");
1570
1662
  warnIfDeprecatedRegion(options);
1663
+ foldRegionOption(options);
1571
1664
  await applyRoleArnIfSet({
1572
1665
  roleArn: options.roleArn,
1573
1666
  region: options.region
@@ -59271,6 +59364,8 @@ async function deployCommand(stacks, options) {
59271
59364
  validateResourceTimeouts(options);
59272
59365
  setResolvedResourceTimeouts(options.resourceTimeout);
59273
59366
  applyWaitFlagEnv(options);
59367
+ const rawBaseRegion = rawCliRegion(options.region) ?? "us-east-1";
59368
+ foldRegionOption(options);
59274
59369
  await applyRoleArnIfSet({
59275
59370
  roleArn: options.roleArn,
59276
59371
  region: options.region
@@ -59282,7 +59377,7 @@ async function deployCommand(stacks, options) {
59282
59377
  const app = resolveApp(options.app);
59283
59378
  if (!app) throw new Error("No app command specified. Use --app, set CDKD_APP env var, or add \"app\" to cdk.json");
59284
59379
  options.app = app;
59285
- const region = options.region || process.env["AWS_REGION"] || "us-east-1";
59380
+ const region = namedCliRegion(options.region) ?? "us-east-1";
59286
59381
  logger.debug("Starting deployment...");
59287
59382
  logger.debug("Options:", options);
59288
59383
  if (options.region) {
@@ -59383,7 +59478,7 @@ async function deployCommand(stacks, options) {
59383
59478
  });
59384
59479
  await synthesizer.expandMacrosForStacks(targetStacks, synthOptions);
59385
59480
  const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
59386
- const stsClient = new STSClient({ region: options.region || process.env["AWS_REGION"] || "us-east-1" });
59481
+ const stsClient = new STSClient({ region: namedCliRegion(options.region) ?? "us-east-1" });
59387
59482
  const accountId = (await stsClient.send(new GetCallerIdentityCommand({}))).Account;
59388
59483
  stsClient.destroy();
59389
59484
  const assetPublisher = new AssetPublisher();
@@ -59407,7 +59502,7 @@ async function deployCommand(stacks, options) {
59407
59502
  };
59408
59503
  const dagBuilder = new DagBuilder({ relaxCdkVpcDefensiveDeps: !!options.aggressiveVpcParallel });
59409
59504
  const diffCalculator = new DiffCalculator();
59410
- const baseRegion = options.region || process.env["AWS_REGION"] || "us-east-1";
59505
+ const baseRegion = namedCliRegion(options.region) ?? "us-east-1";
59411
59506
  const switchRegion = (region) => {
59412
59507
  process.env["AWS_REGION"] = region;
59413
59508
  process.env["AWS_DEFAULT_REGION"] = region;
@@ -59422,7 +59517,7 @@ async function deployCommand(stacks, options) {
59422
59517
  const assetRegion = stack.region || baseRegion;
59423
59518
  const manifest = loadPublishableAssetManifest(stack.assetManifestPath);
59424
59519
  if (manifest) {
59425
- const assetMode = await assetModeResolver.resolve(assetRegion);
59520
+ const assetMode = await assetModeResolver.resolve(stack.region || rawBaseRegion);
59426
59521
  logger.debug(`Asset mode for region ${assetRegion}: ${assetMode.mode} (stack ${stack.stackName})`);
59427
59522
  let redirect;
59428
59523
  if (assetMode.mode === "cdkd-assets") {
@@ -61582,6 +61677,7 @@ async function diffCommand(stacks, options) {
61582
61677
  if (options.json) logger.setLevel("warn");
61583
61678
  else if (options.verbose) logger.setLevel("debug");
61584
61679
  warnIfDeprecatedRegion(options);
61680
+ foldRegionOption(options);
61585
61681
  await applyRoleArnIfSet({
61586
61682
  roleArn: options.roleArn,
61587
61683
  region: options.region
@@ -61589,7 +61685,7 @@ async function diffCommand(stacks, options) {
61589
61685
  const app = resolveApp(options.app);
61590
61686
  if (!app) throw new Error("No app command specified. Use --app, set CDKD_APP env var, or add \"app\" to cdk.json");
61591
61687
  options.app = app;
61592
- const region = options.region || process.env["AWS_REGION"] || "us-east-1";
61688
+ const region = namedCliRegion(options.region) ?? "us-east-1";
61593
61689
  const stateBucket = await resolveStateBucketWithDefault(options.stateBucket, region);
61594
61690
  logger.info("Calculating diff...");
61595
61691
  logger.debug("Options:", options);
@@ -61783,13 +61879,14 @@ async function scrubCommand(stacks, options) {
61783
61879
  const logger = getLogger();
61784
61880
  if (options.verbose) logger.setLevel("debug");
61785
61881
  warnIfDeprecatedRegion(options);
61882
+ foldRegionOption(options);
61786
61883
  await applyRoleArnIfSet({
61787
61884
  roleArn: options.roleArn,
61788
61885
  region: options.region
61789
61886
  });
61790
61887
  const app = resolveApp(options.app);
61791
61888
  if (!app) throw new Error("CDK app is required (scrub needs the template to identify secret references). Pass --app, set CDKD_APP, or add \"app\" to cdk.json.");
61792
- const region = options.region || process.env["AWS_REGION"] || "us-east-1";
61889
+ const region = namedCliRegion(options.region) ?? "us-east-1";
61793
61890
  const stateBucket = await resolveStateBucketWithDefault(options.stateBucket, region);
61794
61891
  setAwsClients(new AwsClients({
61795
61892
  ...options.region && { region: options.region },
@@ -63178,6 +63275,7 @@ async function driftCommand(stacks, options) {
63178
63275
  if (options.verbose) logger.setLevel("debug");
63179
63276
  warnIfDeprecatedRegion(options);
63180
63277
  if (options.accept && options.revert) throw new Error("--accept and --revert are mutually exclusive. Use --accept to update cdkd state from AWS, or --revert to push cdkd state values back into AWS.");
63278
+ foldRegionOption(options);
63181
63279
  await applyRoleArnIfSet({
63182
63280
  roleArn: options.roleArn,
63183
63281
  region: options.region
@@ -63188,7 +63286,7 @@ async function driftCommand(stacks, options) {
63188
63286
  });
63189
63287
  setAwsClients(awsClients);
63190
63288
  try {
63191
- const region = options.region || process.env["AWS_REGION"] || "us-east-1";
63289
+ const region = namedCliRegion(options.region) ?? "us-east-1";
63192
63290
  const bucket = await resolveStateBucketWithDefault(options.stateBucket, region);
63193
63291
  const stateConfig = {
63194
63292
  bucket,
@@ -63407,7 +63505,7 @@ function isSecretBySpelling(token) {
63407
63505
  * token is a reference NAME and carries no value, so it is always safe to show.
63408
63506
  */
63409
63507
  function survivingDynamicReferences(value) {
63410
- return value.match(/\{\{resolve:[^}]+\}\}/g) ?? [];
63508
+ return dynamicReferenceTokens(value);
63411
63509
  }
63412
63510
  /**
63413
63511
  * Record the dotted path of every leaf holding a `{{resolve:...}}` string.
@@ -63549,29 +63647,10 @@ function redactDriftValue(value, secrets, secretBearing) {
63549
63647
  const redacted = redactSecretsForState(value, secrets);
63550
63648
  if (!secretBearing) return redacted;
63551
63649
  if (redacted === void 0 || redacted === null) return redacted;
63552
- if (typeof redacted === "string" && isWholeDynamicReference(redacted)) return redacted;
63650
+ if (typeof redacted === "string" && isSingleDynamicReferenceToken(redacted)) return redacted;
63553
63651
  return "***";
63554
63652
  }
63555
63653
  /**
63556
- * Is the WHOLE string a single `{{resolve:...}}` token and nothing else?
63557
- *
63558
- * Byte-for-byte `secret-redaction.ts`'s `isSingleDynamicReferenceToken`,
63559
- * including the `[^{}]*` character class. The difference from `[^}]+` is
63560
- * narrow and worth stating correctly: it is a `{` INSIDE the token, e.g.
63561
- * `{{resolve:ssm:/a{b}}`. (A previous version of this comment claimed
63562
- * `{{resolve:a}}{{resolve:b}}` would pass under `[^}]+` — it would not, since
63563
- * `[^}]+` cannot span the `}` in the middle either.) Matching the sibling
63564
- * exactly is what matters here: this predicate decides whether a value may be
63565
- * shown INSTEAD of `SECRET_MASK`, and `redactByPath` uses its copy to decide
63566
- * whether a leaf is already an expression, so a disagreement would let one of
63567
- * them treat as a token what the other treats as data. Copied rather than
63568
- * imported because that helper is module-private and this file may not widen
63569
- * that module's exports.
63570
- */
63571
- function isWholeDynamicReference(value) {
63572
- return /^\{\{resolve:[^{}]*\}\}$/.test(value);
63573
- }
63574
- /**
63575
63654
  * Why `--accept` must not persist this drift, or `undefined` when it may.
63576
63655
  *
63577
63656
  * Shared by the WRITE (`runAccept`) and the PLAN (`printAcceptPlan`) so the two
@@ -64296,7 +64375,7 @@ function mergeUntemplatedValue(awsValue, desiredValue) {
64296
64375
  function preserveLiveValuesAtUnresolvedTokens(send, awsProperties, secrets) {
64297
64376
  const walk = (value, live) => {
64298
64377
  if (typeof value === "string" && value.includes("{{resolve:")) {
64299
- if (!isWholeDynamicReference(value)) return value;
64378
+ if (!isSingleDynamicReferenceToken(value)) return value;
64300
64379
  if (live === void 0) return value;
64301
64380
  if (typeof live === "string" && live.length >= MIN_SECRET_NEEDLE_LENGTH && isSecretBySpelling(value)) secrets.set(live, value);
64302
64381
  return live;
@@ -64877,11 +64956,12 @@ async function destroyCommand(stackArgs, options) {
64877
64956
  warnIfDeprecatedRegion(options);
64878
64957
  validateResourceTimeouts(options);
64879
64958
  setResolvedResourceTimeouts(options.resourceTimeout);
64959
+ foldRegionOption(options);
64880
64960
  await applyRoleArnIfSet({
64881
64961
  roleArn: options.roleArn,
64882
64962
  region: options.region
64883
64963
  });
64884
- const region = options.region || process.env["AWS_REGION"] || "us-east-1";
64964
+ const region = namedCliRegion(options.region) ?? "us-east-1";
64885
64965
  const stateBucket = await resolveStateBucketWithDefault(options.stateBucket, region);
64886
64966
  logger.info("Starting stack destruction...");
64887
64967
  logger.debug("Options:", options);
@@ -65156,11 +65236,12 @@ function createDestroyCommand() {
65156
65236
  async function stateMigrateCommand(options) {
65157
65237
  const logger = getLogger();
65158
65238
  if (options.verbose) logger.setLevel("debug");
65239
+ foldRegionOption(options);
65159
65240
  await applyRoleArnIfSet({
65160
65241
  roleArn: options.roleArn,
65161
65242
  region: options.region
65162
65243
  });
65163
- const region = options.region || process.env["AWS_REGION"] || "us-east-1";
65244
+ const region = namedCliRegion(options.region) ?? "us-east-1";
65164
65245
  const awsClients = new AwsClients({
65165
65246
  region,
65166
65247
  ...options.profile && { profile: options.profile }
@@ -66616,11 +66697,12 @@ async function exportCommand(stackArg, options) {
66616
66697
  }
66617
66698
  warnIfDeprecatedRegion(options);
66618
66699
  refuseTransientContextIfUnsafe(options);
66700
+ foldRegionOption(options);
66619
66701
  await applyRoleArnIfSet({
66620
66702
  roleArn: options.roleArn,
66621
66703
  region: options.region
66622
66704
  });
66623
- const region = options.region || process.env["AWS_REGION"] || "us-east-1";
66705
+ const region = namedCliRegion(options.region) ?? "us-east-1";
66624
66706
  const stateBucket = await resolveStateBucketWithDefault(options.stateBucket, region);
66625
66707
  if (options.region) {
66626
66708
  process.env["AWS_REGION"] = options.region;
@@ -69036,6 +69118,7 @@ function resolveSingleRegion(stackName, refs, requestedRegion) {
69036
69118
  */
69037
69119
  async function setupStateBackend(options) {
69038
69120
  warnIfDeprecatedRegion(options);
69121
+ foldRegionOption(options);
69039
69122
  await applyRoleArnIfSet({
69040
69123
  roleArn: options.roleArn,
69041
69124
  region: options.region
@@ -69045,7 +69128,7 @@ async function setupStateBackend(options) {
69045
69128
  ...options.profile && { profile: options.profile }
69046
69129
  });
69047
69130
  setAwsClients(awsClients);
69048
- const region = options.region || process.env["AWS_REGION"] || "us-east-1";
69131
+ const region = namedCliRegion(options.region) ?? "us-east-1";
69049
69132
  const bucket = await resolveStateBucketWithDefault(options.stateBucket, region);
69050
69133
  const prefix = options.statePrefix;
69051
69134
  const stateConfig = {
@@ -69936,6 +70019,7 @@ async function listAssetStorageMarkers(s3, bucket) {
69936
70019
  async function stateInfoCommand(options) {
69937
70020
  const logger = getLogger();
69938
70021
  if (options.verbose) logger.setLevel("debug");
70022
+ foldRegionOption(options);
69939
70023
  await applyRoleArnIfSet({
69940
70024
  roleArn: options.roleArn,
69941
70025
  region: options.region
@@ -69947,7 +70031,7 @@ async function stateInfoCommand(options) {
69947
70031
  setAwsClients(awsClients);
69948
70032
  let regionCorrectedS3 = null;
69949
70033
  try {
69950
- const region = options.region || process.env["AWS_REGION"] || "us-east-1";
70034
+ const region = namedCliRegion(options.region) ?? "us-east-1";
69951
70035
  const resolved = await resolveStateBucketWithDefaultAndSource(options.stateBucket, region);
69952
70036
  const bucket = resolved.bucket;
69953
70037
  const prefix = options.statePrefix;
@@ -70682,6 +70766,7 @@ async function eventsCommand(stackName, options) {
70682
70766
  const logger = getLogger();
70683
70767
  if (options.verbose) logger.setLevel("debug");
70684
70768
  warnIfDeprecatedRegion(options);
70769
+ foldRegionOption(options);
70685
70770
  const asJson = options.json === true;
70686
70771
  const awsClients = new AwsClients({
70687
70772
  ...options.region && { region: options.region },
@@ -70689,7 +70774,7 @@ async function eventsCommand(stackName, options) {
70689
70774
  });
70690
70775
  setAwsClients(awsClients);
70691
70776
  try {
70692
- const region = options.region || process.env["AWS_REGION"] || "us-east-1";
70777
+ const region = namedCliRegion(options.region) ?? "us-east-1";
70693
70778
  const bucket = await resolveStateBucketWithDefault(options.stateBucket, region);
70694
70779
  const prefix = options.statePrefix ?? "cdkd";
70695
70780
  const stateBackend = new S3StateBackend(awsClients.s3, {
@@ -70759,6 +70844,7 @@ async function eventsPruneCommand(stackName, options) {
70759
70844
  const logger = getLogger();
70760
70845
  if (options.verbose) logger.setLevel("debug");
70761
70846
  warnIfDeprecatedRegion(options);
70847
+ foldRegionOption(options);
70762
70848
  if (options.all === true && (options.keep !== void 0 || options.olderThan !== void 0)) throw new CdkdError("'--all' purges every run and cannot be combined with '--keep' / '--older-than'.", "EVENTS_PRUNE_BAD_FLAGS");
70763
70849
  const olderThanMs = options.olderThan !== void 0 ? parseDuration(options.olderThan) : void 0;
70764
70850
  const awsClients = new AwsClients({
@@ -70767,7 +70853,7 @@ async function eventsPruneCommand(stackName, options) {
70767
70853
  });
70768
70854
  setAwsClients(awsClients);
70769
70855
  try {
70770
- const region = options.region || process.env["AWS_REGION"] || "us-east-1";
70856
+ const region = namedCliRegion(options.region) ?? "us-east-1";
70771
70857
  const bucket = await resolveStateBucketWithDefault(options.stateBucket, region);
70772
70858
  const prefix = options.statePrefix ?? "cdkd";
70773
70859
  const stateBackend = new S3StateBackend(awsClients.s3, {
@@ -71819,7 +71905,7 @@ function formatAge(date) {
71819
71905
  async function promptGcConfirm(input) {
71820
71906
  const logger = getLogger();
71821
71907
  logger.warn("");
71822
- logger.warn("cdkd gc will delete the following unreferenced assets:");
71908
+ logger.warn(`cdkd gc will delete the following unreferenced assets in ${input.region}:`);
71823
71909
  for (const line of input.planLines) logger.warn(` - ${line}`);
71824
71910
  if (input.yes) return true;
71825
71911
  if (process.stdin.isTTY !== true) throw new CdkdError("The gc confirmation prompt cannot run in a non-interactive environment. Pass --yes / -y to confirm the deletion, or run the command from a real terminal.", "NON_INTERACTIVE_CONFIRM");
@@ -71882,10 +71968,10 @@ async function gcCommand(options) {
71882
71968
  logger.debug("Options:", options);
71883
71969
  await applyRoleArnIfSet({
71884
71970
  roleArn: options.roleArn,
71885
- region: canonicalizeRegion(options.region)
71971
+ region: namedCliRegion(options.region)
71886
71972
  });
71887
- const rawRegion = options.region || process.env["AWS_REGION"] || "us-east-1";
71888
- const region = canonicalizeRegion(rawRegion);
71973
+ const region = namedCliRegion(options.region) ?? "us-east-1";
71974
+ const rawRegion = rawCliRegion(options.region) ?? region;
71889
71975
  const awsClients = new AwsClients({
71890
71976
  region,
71891
71977
  ...options.profile && { profile: options.profile }
@@ -71949,7 +72035,7 @@ async function gcCommand(options) {
71949
72035
  const totals = `Total: ${s3Candidates.length} S3 object(s) (${formatBytes(s3Bytes)}) + ${ecrCandidates.length} ECR image(s) (${formatBytes(ecrBytes)}) = ${formatBytes(s3Bytes + ecrBytes)} reclaimable`;
71950
72036
  if (options.dryRun) {
71951
72037
  logger.info("");
71952
- logger.info("Dry run — the following unreferenced assets would be deleted:");
72038
+ logger.info(`Dry run — the following unreferenced assets in ${region} would be deleted:`);
71953
72039
  for (const line of planLines) logger.info(` - ${line}`);
71954
72040
  logger.info(totals);
71955
72041
  logger.info("Dry run: nothing deleted. Re-run without --dry-run to delete.");
@@ -71957,7 +72043,8 @@ async function gcCommand(options) {
71957
72043
  }
71958
72044
  if (!await promptGcConfirm({
71959
72045
  planLines: [...planLines, totals],
71960
- yes: options.yes
72046
+ yes: options.yes,
72047
+ region
71961
72048
  })) {
71962
72049
  logger.info("gc cancelled — nothing deleted.");
71963
72050
  return;
@@ -71981,7 +72068,7 @@ async function gcCommand(options) {
71981
72068
  * Create the `cdkd gc` command (upstream `cdk gc` parity naming).
71982
72069
  */
71983
72070
  function createGcCommand() {
71984
- const cmd = new Command("gc").description("Garbage-collect unreferenced objects / images from ONE region's cdkd-owned asset storage (asset bucket + container-asset ECR repo). References are collected from every cdkd state file; CDK bootstrap storage is never touched (use cdk gc for that).").option("--state-bucket <bucket>", "S3 bucket holding cdkd state (default: cdkd-state-{accountId})").addOption(new Option("--older-than <duration>", "Never delete an object / image newer than this age, even when unreferenced (protects in-flight publishes and recent rollback targets). Accepts <n>d / <n>h.").default(DEFAULT_OLDER_THAN_MS, "30d").argParser(parseOlderThan)).option("--dry-run", "Print the reclaim plan (per-item list + totals) without deleting", false).addOption(new Option("--region <region>", "Region whose cdkd asset storage to garbage-collect (defaults to AWS_REGION env or us-east-1)")).action(withErrorHandling(async (options) => {
72071
+ const cmd = new Command("gc").description("Garbage-collect unreferenced objects / images from ONE region's cdkd-owned asset storage (asset bucket + container-asset ECR repo). References are collected from every cdkd state file; CDK bootstrap storage is never touched (use cdk gc for that).").option("--state-bucket <bucket>", "S3 bucket holding cdkd state (default: cdkd-state-{accountId})").addOption(new Option("--older-than <duration>", "Never delete an object / image newer than this age, even when unreferenced (protects in-flight publishes and recent rollback targets). Accepts <n>d / <n>h.").default(DEFAULT_OLDER_THAN_MS, "30d").argParser(parseOlderThan)).option("--dry-run", "Print the reclaim plan (per-item list + totals) without deleting", false).addOption(new Option("--region <region>", "Region whose cdkd asset storage to garbage-collect (defaults to AWS_REGION / AWS_DEFAULT_REGION, else the profile region; refuses if none is configured)")).action(withErrorHandling(async (options) => {
71985
72072
  await gcCommand(options);
71986
72073
  }));
71987
72074
  commonOptions.forEach((opt) => cmd.addOption(opt));
@@ -72481,13 +72568,14 @@ async function orphanCommand(pathArgs, options) {
72481
72568
  const logger = getLogger();
72482
72569
  if (options.verbose) logger.setLevel("debug");
72483
72570
  warnIfDeprecatedRegion(options);
72571
+ foldRegionOption(options);
72484
72572
  await applyRoleArnIfSet({
72485
72573
  roleArn: options.roleArn,
72486
72574
  region: options.region
72487
72575
  });
72488
72576
  if (pathArgs.length === 0) throw new Error("'cdkd orphan' requires at least one construct path, e.g. 'cdkd orphan MyStack/MyTable'.\n To remove a stack's state record (the previous behavior), use:\n cdkd state orphan MyStack");
72489
72577
  for (const p of pathArgs) if (!p.includes("/")) throw new Error(`'cdkd orphan' now expects a construct path like 'MyStack/MyTable'.\n Got: '${p}'\n To remove a stack's state record (the previous behavior), use:\n cdkd state orphan ${p}`);
72490
- const region = options.region || process.env["AWS_REGION"] || "us-east-1";
72578
+ const region = namedCliRegion(options.region) ?? "us-east-1";
72491
72579
  const stateBucket = await resolveStateBucketWithDefault(options.stateBucket, region);
72492
72580
  if (options.region) {
72493
72581
  process.env["AWS_REGION"] = options.region;
@@ -72724,6 +72812,8 @@ async function publishAssetsCommand(stacks, options) {
72724
72812
  const logger = getLogger();
72725
72813
  if (options.verbose) logger.setLevel("debug");
72726
72814
  warnIfDeprecatedRegion(options);
72815
+ const rawBaseRegion = rawCliRegion(options.region) ?? "us-east-1";
72816
+ foldRegionOption(options);
72727
72817
  await applyRoleArnIfSet({
72728
72818
  roleArn: options.roleArn,
72729
72819
  region: options.region
@@ -72749,7 +72839,7 @@ async function publishAssetsCommand(stacks, options) {
72749
72839
  else if (allStacks.length === 1) targetStacks = allStacks;
72750
72840
  else throw new Error(`Multiple stacks found: ${allStacks.map(describeStack).join(", ")}. Specify stack name(s) or use --all`);
72751
72841
  if (targetStacks.length === 0) throw new Error(stackPatterns.length > 0 ? `No stacks matching ${stackPatterns.join(", ")} found in assembly. Available: ${allStacks.map(describeStack).join(", ")}` : "No stacks found in assembly");
72752
- const baseRegion = options.region || process.env["AWS_REGION"] || "us-east-1";
72842
+ const baseRegion = namedCliRegion(options.region) ?? "us-east-1";
72753
72843
  const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
72754
72844
  const stsClient = new STSClient({ region: baseRegion });
72755
72845
  const accountId = (await stsClient.send(new GetCallerIdentityCommand({}))).Account;
@@ -72809,7 +72899,7 @@ async function publishAssetsCommand(stacks, options) {
72809
72899
  const modeResolver = await getModeResolver();
72810
72900
  if (modeResolver) {
72811
72901
  const stackRegion = stack.region || baseRegion;
72812
- const assetMode = await modeResolver.resolve(stackRegion);
72902
+ const assetMode = await modeResolver.resolve(stack.region || rawBaseRegion);
72813
72903
  if (assetMode.mode === "cdkd-assets") {
72814
72904
  const map = buildAssetRedirectMap(manifest, assetMode.marker, accountId, stackRegion);
72815
72905
  if (map.entries.length > 0) redirect = map;
@@ -72917,6 +73007,7 @@ async function forceUnlockCommand(stackArgs, options) {
72917
73007
  const logger = getLogger();
72918
73008
  if (options.verbose) logger.setLevel("debug");
72919
73009
  warnIfDeprecatedRegion(options);
73010
+ foldRegionOption(options);
72920
73011
  await applyRoleArnIfSet({
72921
73012
  roleArn: options.roleArn,
72922
73013
  region: options.region
@@ -72928,7 +73019,7 @@ async function forceUnlockCommand(stackArgs, options) {
72928
73019
  ...options.profile && { profile: options.profile }
72929
73020
  });
72930
73021
  setAwsClients(awsClients);
72931
- const region = options.region || process.env["AWS_REGION"] || "us-east-1";
73022
+ const region = namedCliRegion(options.region) ?? "us-east-1";
72932
73023
  const stateBucket = await resolveStateBucketWithDefault(options.stateBucket, region);
72933
73024
  try {
72934
73025
  const stateConfig = {
@@ -72987,11 +73078,12 @@ async function importCommand(stackArg, options) {
72987
73078
  logger.setLevel("debug");
72988
73079
  process.env["CDKD_NO_LIVE"] = "1";
72989
73080
  }
73081
+ foldRegionOption(options);
72990
73082
  await applyRoleArnIfSet({
72991
73083
  roleArn: options.roleArn,
72992
73084
  region: options.region
72993
73085
  });
72994
- const region = options.region || process.env["AWS_REGION"] || "us-east-1";
73086
+ const region = namedCliRegion(options.region) ?? "us-east-1";
72995
73087
  const stateBucket = await resolveStateBucketWithDefault(options.stateBucket, region);
72996
73088
  if (options.region) {
72997
73089
  process.env["AWS_REGION"] = options.region;
@@ -83581,11 +83673,12 @@ async function migrateCommandAction(positionalStack, options) {
83581
83673
  if (options.retireCfnStack && options.skipSynth) throw new LocalMigrateError("--retire-cfn-stack is incompatible with --skip-synth: the post-state-write retirement (UpdateStack + DeleteStack) cannot run without a synthesized template + cdkd state.");
83582
83674
  if (options.retireCfnStack && options.dryRun) throw new LocalMigrateError("--retire-cfn-stack is incompatible with --dry-run: retirement issues real AWS calls (UpdateStack injects Retain policies, then DeleteStack).");
83583
83675
  if (options.retireCfnStack && options.filter && options.filter.length > 0) throw new LocalMigrateError("--retire-cfn-stack is incompatible with --filter: a partial migration that retires the whole source CFn stack would strand the un-migrated resources. Drop one of the flags or migrate the rest of the stack first.");
83676
+ foldRegionOption(options);
83584
83677
  await applyRoleArnIfSet({
83585
83678
  roleArn: options.roleArn,
83586
83679
  region: options.region
83587
83680
  });
83588
- const region = options.region || process.env["AWS_REGION"] || "us-east-1";
83681
+ const region = namedCliRegion(options.region) ?? "us-east-1";
83589
83682
  const outputDir = resolve(options.outputDir ?? sourceCfnStackName);
83590
83683
  logger.info(`[migrate] Source CFn stack: ${sourceCfnStackName}`);
83591
83684
  logger.info(`[migrate] Output directory: ${outputDir}`);
@@ -83750,7 +83843,7 @@ function createMigrateCommand() {
83750
83843
  */
83751
83844
  function buildProgram() {
83752
83845
  const program = new Command();
83753
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.19");
83846
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.21");
83754
83847
  program.hook("preAction", (_thisCommand, actionCommand) => {
83755
83848
  const { profile } = actionCommand.optsWithGlobals();
83756
83849
  if (profile !== void 0) process.env["AWS_PROFILE"] = profile;