@go-to-k/cdkd 0.284.38 → 0.284.40

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 startInterruptWatch, $t as buildAssetRedirectMap, A as renderStatefulReason, An as resolveStateBucketWithDefaultAndSource, At as redactSecretsForState, B as exportAliasCollisionScrubWarning, Bn as PARTITION_TABLE, Bt as DiffCalculator, C as isFinalSnapshotError, Cn as getDefaultStateBucketName, Cr as isRetryableTransientError, Ct as TEMPLATE_SOURCED_RULES, D as extractDeploymentEventError, Dn as resolveCaptureObservedState, Dt as isSingleDynamicReferenceToken, E as makeCanonicalizePropertiesFn, En as resolveAutoAssetStorage, Et as errorCauseChain, F as green, Fn as CFN_TEMPLATE_URL_LIMIT, Ft as s3BucketDomainName, G as collectInlinePolicyNamesManagedBySiblings, Gt as TemplateParser, H as secretBearingStateKeyWarning, Hn as derivePartitionAndUrlSuffix, Ht as describeTypeWithThrottleRetry, I as red, In as MIGRATE_TMP_PREFIX, It as s3BucketDualStackDomainName, J as findActionableSilentDrops, Jn as getAwsClients, Jt as rebuildClientForBucketRegion, K as clearOnUpdateRemoval, Kn as resolveBucketRegion, Kt as LockManager, L as yellow, Ln as findLargeInlineResources, Lt as s3BucketRegionalDomainName, M as bold, Mn as stateBucketExistenceConfirmed, Mt as classifyReplaySecretRegion, N as cyan, Nn as warnDeprecatedNoPrefixCliFlag, Nt as producerRegionsFromState, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as resolveSkipPrefix, Ot as maskSecretsInError, P as gray, Pn as CFN_TEMPLATE_BODY_LIMIT, Pt as s3BucketArn, Q as isInterruptedWaitError, Qn as CdkdError, Qt as WorkGraph, R as collectDeclaredOutputNames, Rn as uploadCfnTemplate, Rt as s3BucketWebsiteUrl, S as createPreDeleteFinalSnapshot, Sn as synthesisStatusMessage, Sr as isMarkedNonRetryable, St as STATE_SOURCED_READBACK_RULES, T as unsupportedFinalSnapshotError, Tn as resolveApp, Tr as markNonRetryable, Tt as dynamicReferenceTokens, U as stateKeySecretExposure, Un as AssemblyReader, Ut as withRetry, V as isExportAliasCollision, Vn as canonicalizeRegion, Vt as INTRINSIC_KEYS, W as IAMRoleProvider, Wn as processStackMessages, Wt as DagBuilder, X as beginCommandInterruptScope, Xn as setAwsClients, Xt as AssetPublisher, Y as findSilentDropProperties, Yn as resetAwsClients, Yt as shouldRetainResource, Z as endCommandInterruptScope, Zt as stringifyValue, _ as computeImplicitDeleteEdges, _n as runDockerForeground, _t as replayWarn, a as DeploymentEventsStore, an as AssetModeResolver, ar as LocalMigrateError, at as carriesDynamicReference, b as buildFinalSnapshotIdentifier, bn as getDockerImageBySourceHash, br as normalizeAwsError, bt as requireConfigString, c as replayFailedOperations, cn as getBootstrapMarkerKey, cr as MissingCdkCliError, ct as refStateLookupFromResource, d as updatePartialReason, dn as validateAssetBucketName, dr as ProvisioningError, dt as resolveExplicitPhysicalId, en as createAssetRedirectResolver, er as CrossAccountSecretRefusalError, et as CloudControlProvider, f as UNSPECIFIED_SKIP_REASON, fn as validateContainerRepoName, fr as ResourceTimeoutError, ft as assertRegionMatch, g as IMPLICIT_DELETE_DEPENDENCIES, gn as getDockerCmd, gt as readConfigString, h as maskingRetryLogger, hn as formatDockerLoginError, hr as StackTerminationProtectionError, ht as configStringRefusal, i as DeploymentEventsReader, in as stripControlChars, ir as LocalInvokeBuildError$1, it as IntrinsicFunctionResolver, j as formatResourceLine, jn as resolveUseCdkBootstrapAssets, jt as scrubResourceRecord, k as isStatefulRecreateTargetSync, kn as resolveStateBucketWithDefault, kt as maskSecretsInText, l as replayRollback, ln as parseBootstrapMarker, lr as NestedStackChildDirectDestroyError, lt as WAFv2WebACLProvider, m as withResourceDeadline, mn as buildDockerImage, mr as StackHasActiveImportsError, mt as configBooleanRefusal, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as rewriteTemplateAssetReferences, nr as DeployCancelledError, nt as disableInstanceApiTermination, o as planFailedOps, on as BOOTSTRAP_MARKER_PREFIX, or as LocalStartServiceError, ot as cfnRefValueFromPhysicalId, p as deleteSkipReason, pn as buildDenyExternalAccessPolicy, pr as ResourceUpdateNotSupportedError, pt as coerceCfnBoolean, q as ProviderRegistry, qn as AwsClients, qt as S3StateBackend, r as DeployEngine, rn as escapeRegExp, rr as DynamicReferenceRegionAmbiguousError, rt as isTerminationProtectionPropagationError, s as planRollback, sn as ensureAssetStorage, st as getAccountInfo, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as loadPublishableAssetManifest, tt as slowCcOperationTimeoutMs, u as updatePartialMessage, un as readBootstrapMarkerBody, ur as PartialFailureError, ut as normalizeAwsTagsToCfn, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as runDockerStreaming, vt as requireConfigArray, w as refusesFinalSnapshot, wn as getLegacyStateBucketName, wr as isThrottlingError, wt as createSecretMasker, x as ccRoutedFinalSnapshotError, xn as Synthesizer, xr as withErrorHandling, xt as STATE_SOURCED_CROSS_GENERATION_RULES, y as PRE_DELETE_SNAPSHOT_TYPES, yn as AssetManifestLoader, yt as requireConfigObject, z as collectPublishedOutputNames, zn as expectedOwnerParam, zt as applyRoleArnIfSet } from "./deploy-engine-DyxASW-O.js";
2
+ import { $ as startInterruptWatch, $n as CdkdError, $t as WorkGraph, A as renderStatefulReason, An as resolveStateBucketWithDefault, At as redactSecretsForState, B as exportAliasCollisionScrubWarning, Bn as expectedOwnerParam, Bt as DiffCalculator, C as isFinalSnapshotError, Cn as synthesisStatusMessage, Cr as isMarkedNonRetryable, Ct as TEMPLATE_SOURCED_RULES, D as extractDeploymentEventError, Dn as resolveAutoAssetStorage, Dt as isSingleDynamicReferenceToken, E as makeCanonicalizePropertiesFn, En as resolveApp, Er as markNonRetryable, Et as errorCauseChain, F as green, Fn as CFN_TEMPLATE_BODY_LIMIT, Ft as s3BucketDomainName, G as collectInlinePolicyNamesManagedBySiblings, Gn as processStackMessages, Gt as TemplateParser, H as secretBearingStateKeyWarning, Hn as canonicalizeRegion, Ht as describeTypeWithThrottleRetry, I as red, In as CFN_TEMPLATE_URL_LIMIT, It as s3BucketDualStackDomainName, J as findActionableSilentDrops, Jn as AwsClients, Jt as S3StateBackend, K as clearOnUpdateRemoval, Kt as LockManager, L as yellow, Ln as MIGRATE_TMP_PREFIX, Lt as s3BucketRegionalDomainName, M as bold, Mn as resolveUseCdkBootstrapAssets, Mt as classifyReplaySecretRegion, N as cyan, Nn as stateBucketExistenceConfirmed, Nt as producerRegionsFromState, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as resolveCaptureObservedState, Ot as maskSecretsInError, P as gray, Pn as warnDeprecatedNoPrefixCliFlag, Pt as s3BucketArn, Q as isInterruptedWaitError, Qt as stringifyValue, R as collectDeclaredOutputNames, Rn as findLargeInlineResources, Rt as s3BucketWebsiteUrl, S as createPreDeleteFinalSnapshot, Sn as Synthesizer, Sr as withErrorHandling, St as STATE_SOURCED_READBACK_RULES, T as unsupportedFinalSnapshotError, Tn as getLegacyStateBucketName, Tr as isThrottlingError, Tt as dynamicReferenceTokens, U as stateKeySecretExposure, Un as derivePartitionAndUrlSuffix, Ut as withRetry, V as isExportAliasCollision, Vn as PARTITION_TABLE, Vt as INTRINSIC_KEYS, W as IAMRoleProvider, Wn as AssemblyReader, Wt as DagBuilder, X as beginCommandInterruptScope, Xn as resetAwsClients, Xt as shouldRetainResource, Y as findSilentDropProperties, Yn as getAwsClients, Yt as rebuildClientForBucketRegion, Z as endCommandInterruptScope, Zn as setAwsClients, Zt as AssetPublisher, _ as computeImplicitDeleteEdges, _n as getDockerCmd, _t as replayWarn, a as DeploymentEventsStore, an as stripControlChars, ar as LocalInvokeBuildError$1, at as carriesDynamicReference, b as buildFinalSnapshotIdentifier, bn as AssetManifestLoader, bt as requireConfigString, c as replayFailedOperations, cn as ensureAssetStorage, ct as refStateLookupFromResource, d as updatePartialReason, dn as readBootstrapMarkerBody, dr as PartialFailureError, dt as resolveExplicitPhysicalId, en as buildAssetRedirectMap, et as CloudControlProvider, f as UNSPECIFIED_SKIP_REASON, fn as validateAssetBucketName, fr as ProvisioningError, ft as assertRegionMatch, g as IMPLICIT_DELETE_DEPENDENCIES, gn as formatDockerLoginError, gr as StackTerminationProtectionError, gt as readConfigString, h as maskingRetryLogger, hn as buildDockerImage, hr as StackHasActiveImportsError, ht as configStringRefusal, i as DeploymentEventsReader, in as escapeRegExp, ir as DynamicReferenceRegionAmbiguousError, it as IntrinsicFunctionResolver, j as formatResourceLine, jn as resolveStateBucketWithDefaultAndSource, jt as scrubResourceRecord, k as isStatefulRecreateTargetSync, kn as resolveSkipPrefix, kt as maskSecretsInText, l as replayRollback, ln as getBootstrapMarkerKey, lr as MissingCdkCliError, lt as WAFv2WebACLProvider, m as withResourceDeadline, mn as buildDenyExternalAccessPolicy, mr as ResourceUpdateNotSupportedError, mt as configBooleanRefusal, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as loadPublishableAssetManifest, nt as disableInstanceApiTermination, o as planFailedOps, on as AssetModeResolver, or as LocalMigrateError, ot as cfnRefValueFromPhysicalId, p as deleteSkipReason, pn as validateContainerRepoName, pr as ResourceTimeoutError, pt as coerceCfnBoolean, q as ProviderRegistry, qn as resolveBucketRegion, qt as displaySafe, r as DeployEngine, rn as rewriteTemplateAssetReferences, rr as DeployCancelledError, rt as isTerminationProtectionPropagationError, s as planRollback, sn as BOOTSTRAP_MARKER_PREFIX, sr as LocalStartServiceError, st as getAccountInfo, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as createAssetRedirectResolver, tr as CrossAccountSecretRefusalError, tt as slowCcOperationTimeoutMs, u as updatePartialMessage, un as parseBootstrapMarker, ur as NestedStackChildDirectDestroyError, ut as normalizeAwsTagsToCfn, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as runDockerForeground, vt as requireConfigArray, w as refusesFinalSnapshot, wn as getDefaultStateBucketName, wr as isRetryableTransientError, wt as createSecretMasker, x as ccRoutedFinalSnapshotError, xn as getDockerImageBySourceHash, xr as normalizeAwsError, xt as STATE_SOURCED_CROSS_GENERATION_RULES, y as PRE_DELETE_SNAPSHOT_TYPES, yn as runDockerStreaming, yt as requireConfigObject, z as collectPublishedOutputNames, zn as uploadCfnTemplate, zt as applyRoleArnIfSet } from "./deploy-engine-B3MQFW1K.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-kqBWElNc.js";
4
+ import { t as ASGProvider } from "./asg-provider-BeKDzUe9.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";
@@ -77,6 +77,23 @@ import { setTimeout as setTimeout$1 } from "node:timers/promises";
77
77
  import { mkdtemp, rm, writeFile } from "node:fs/promises";
78
78
  import { WebSocketServer } from "ws";
79
79
 
80
+ //#region src/state/state-prefix.ts
81
+ /**
82
+ * The default S3 key prefix for cdkd state.
83
+ *
84
+ * Homed in the STATE layer rather than in `src/cli/commands/state-file-keys.ts`
85
+ * (which re-exports it, so its four existing importers are unchanged) because
86
+ * `src/state/lock-contention-message.ts` needs it to decide whether a recovery
87
+ * hint should spell `--state-prefix` at all, and a `src/state/**` module
88
+ * importing from `src/cli/commands/**` inverts the layering — the CLI sits
89
+ * ABOVE the state layer in the 7-layer architecture, not below it.
90
+ *
91
+ * Note this is only the DEFAULT. Other commands accept `--state-prefix`, so
92
+ * whole-bucket listings deliberately do not scope to it.
93
+ */
94
+ const DEFAULT_STATE_PREFIX = "cdkd";
95
+
96
+ //#endregion
80
97
  //#region src/cli/commands/state-file-keys.ts
81
98
  /**
82
99
  * Shared helpers for enumerating and describing cdkd state-bucket keys.
@@ -94,20 +111,6 @@ import { WebSocketServer } from "ws";
94
111
  */
95
112
  const STATE_FILE_SUFFIX = "/state.json";
96
113
  /**
97
- * The prefix `cdkd bootstrap` writes under, and the one `gc` /
98
- * `bootstrap --destroy` configure their own state backend with.
99
- *
100
- * Shared here rather than spelled per command for the same reason this module
101
- * exists at all: `bootstrap-destroy.ts` and `gc.ts` each held their own copy,
102
- * and it is now also the `knownPrefix` argument to {@link describeStateKey},
103
- * where a copy that drifted from the backend's actual prefix would silently
104
- * send every key back to the shape heuristic instead of the exact depth rule.
105
- *
106
- * Note this is only the DEFAULT. Other commands accept `--state-prefix`, so
107
- * the whole-bucket listings deliberately do not scope to it.
108
- */
109
- const DEFAULT_STATE_PREFIX = "cdkd";
110
- /**
111
114
  * Segments left under a known prefix for the legacy `{prefix}/{stack}` layout.
112
115
  *
113
116
  * The twin of `S3StateBackend`'s `LEGACY_KEY_DEPTH`, which counts the
@@ -23916,9 +23919,10 @@ const generations = /* @__PURE__ */ new Map();
23916
23919
  * long-lived process cannot grow the map without bound.
23917
23920
  */
23918
23921
  const MAX_TRACKED_GENERATIONS = 1e5;
23919
- const tokenKey = (scope, logicalId) => `${scope}\u0000${process.env["AWS_REGION"] ?? ""}\u0000${getCurrentStackName() ?? ""}\u0000${logicalId}`;
23920
- const derive = (key, generation, maxLength) => {
23921
- return `cdkd-${createHash("sha256").update(`${PROCESS_NONCE}\u0000${key}\u0000${generation}`).digest("hex")}`.slice(0, maxLength);
23922
+ const tokenKey = (scope, logicalId, maxLength, charset) => `${scope}\u0000${process.env["AWS_REGION"] ?? ""}\u0000${getCurrentStackName() ?? ""}\u0000${logicalId}\u0000${maxLength}\u0000${charset}`;
23923
+ const derive = (key, generation, maxLength, charset) => {
23924
+ const digest = createHash("sha256").update(`${PROCESS_NONCE}\u0000${key}\u0000${generation}`).digest("hex");
23925
+ return `cdkd${charset === "alphanumeric" ? "" : "-"}${digest}`.slice(0, maxLength);
23922
23926
  };
23923
23927
  /**
23924
23928
  * Get the idempotency token for one logical create, minting it on first use and
@@ -23932,10 +23936,10 @@ const derive = (key, generation, maxLength) => {
23932
23936
  * ```
23933
23937
  */
23934
23938
  function acquireIdempotencyToken(options) {
23935
- const { scope, logicalId, maxLength = 64 } = options;
23936
- const key = tokenKey(scope, logicalId);
23939
+ const { scope, logicalId, maxLength = 64, charset = "default" } = options;
23940
+ const key = tokenKey(scope, logicalId, maxLength, charset);
23937
23941
  const existing = inFlight.get(key);
23938
- const value = existing ?? derive(key, generations.get(key) ?? 0, maxLength);
23942
+ const value = existing ?? derive(key, generations.get(key) ?? 0, maxLength, charset);
23939
23943
  if (existing === void 0) inFlight.set(key, value);
23940
23944
  return {
23941
23945
  value,
@@ -58162,6 +58166,104 @@ var ECRProvider = class {
58162
58166
  }
58163
58167
  };
58164
58168
 
58169
+ //#endregion
58170
+ //#region src/state/lock-contention-message.ts
58171
+ /**
58172
+ * The message every fail-fast lock-contention site raises.
58173
+ *
58174
+ * Issue #2161 made six commands throw on `acquireLock` returning `false`
58175
+ * instead of running under a foreign lock. Issue #2170 is the follow-up: the
58176
+ * message those sites raise asked the user to decide "is another process
58177
+ * active?" while printing none of the evidence that would answer it, and the
58178
+ * recovery command it suggested could resolve against the wrong AWS account.
58179
+ *
58180
+ * Both matter more than they look, because of WHICH locks reach this message.
58181
+ * `LockManager.acquireLock` reaps an EXPIRED foreign lock and retries, so a
58182
+ * `false` return means the lock is LIVE — in practice a running `cdkd deploy`.
58183
+ * The user who follows a bare `cdkd force-unlock <stack>` suggestion therefore
58184
+ * deletes a live owner's lock and reproduces issue #2161's harm by hand, which
58185
+ * is the outcome #2161 exists to prevent.
58186
+ *
58187
+ * Centralising the text here also settles the third finding: the nine sites
58188
+ * had drifted to three spellings (`for stack` / `for nested stack` /
58189
+ * `for nested-stack child`), so a user grepping CI logs for one of them found
58190
+ * two of three. `subject` now varies only the noun.
58191
+ */
58192
+ /**
58193
+ * Stand-in for a value with nothing renderable left after sanitization. Named
58194
+ * rather than inlined so the message and the command-suppression branch cannot
58195
+ * disagree about what "unrenderable" looks like.
58196
+ */
58197
+ const UNRENDERABLE = "<unrenderable>";
58198
+ /** Render `expiresIn` without implying more precision than a clock skew allows. */
58199
+ function formatRemaining(ms) {
58200
+ if (!Number.isFinite(ms)) return "at an unknown time";
58201
+ if (ms <= 0) return "already expired";
58202
+ const minutes = Math.round(ms / 6e4);
58203
+ if (minutes < 1) return "in under a minute";
58204
+ return `in ~${minutes}m`;
58205
+ }
58206
+ function shellQuote(value) {
58207
+ return /^[A-Za-z0-9._/@:+-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
58208
+ }
58209
+ /**
58210
+ * The `cdkd force-unlock ...` line, carrying every flag that decides which
58211
+ * lock object it resolves to.
58212
+ */
58213
+ function buildForceUnlockCommand(stackName, region, recovery) {
58214
+ const safeStack = displaySafe(stackName, { asciiOnly: true });
58215
+ const stackIsExact = safeStack === stackName;
58216
+ const safeRegion = region === void 0 ? void 0 : displaySafe(region, { asciiOnly: true });
58217
+ if (!safeStack || safeRegion === "" || !stackIsExact || !(region === void 0 || safeRegion === region)) return "";
58218
+ const parts = [safeRegion === void 0 ? `cdkd force-unlock ${shellQuote(safeStack)}` : `cdkd force-unlock ${shellQuote(safeStack)} --stack-region ${shellQuote(safeRegion)}`];
58219
+ if (recovery?.profile) parts.push(`--profile ${shellQuote(recovery.profile)}`);
58220
+ if (recovery?.stateBucket) parts.push(`--state-bucket ${shellQuote(recovery.stateBucket)}`);
58221
+ if (recovery?.statePrefix && recovery.statePrefix !== "cdkd") parts.push(`--state-prefix ${shellQuote(recovery.statePrefix)}`);
58222
+ return parts.join(" ");
58223
+ }
58224
+ /**
58225
+ * The force-quit banner's recovery sentence.
58226
+ *
58227
+ * Exported so the two `destroy-runner.ts` banners do not each decide what to
58228
+ * say when {@link buildForceUnlockCommand} suppresses — a banner ending in a
58229
+ * bare `run: ` is the shape the review found, and two copies of the branch is
58230
+ * how the next one drifts. Returns a leading-space clause so the caller can
58231
+ * concatenate it unconditionally.
58232
+ */
58233
+ function forceQuitRecoveryClause(stackName, region, recovery) {
58234
+ const command = buildForceUnlockCommand(stackName, region, recovery);
58235
+ return command ? ` If the next run reports a lock, run: ${command}` : " Inspect the lock object directly: the name or region recorded for this stack cannot be reproduced safely on a command line, so any command shown here would address a different lock.";
58236
+ }
58237
+ /**
58238
+ * Build the contention message, reading the holder's identity best-effort.
58239
+ *
58240
+ * The `getLockInfo` read is one GetObject and is deliberately NOT allowed to
58241
+ * fail the command: the caller is already on its way to throwing, and turning
58242
+ * a contention refusal into an S3 error would lose the reason. A failed or
58243
+ * absent read degrades to the evidence-free wording rather than to a crash.
58244
+ */
58245
+ async function buildLockContentionMessage(args) {
58246
+ const { lockManager, stackName, region, subject = "stack", recovery, heldClause, suffix } = args;
58247
+ let held = heldClause ?? "another cdkd process holds it";
58248
+ let sawHolder = false;
58249
+ try {
58250
+ const info = await lockManager.getLockInfo(stackName, region);
58251
+ if (info) {
58252
+ const operation = info.operation ? `, operation: ${displaySafe(info.operation)}` : "";
58253
+ const expires = formatRemaining(info.expiresAt - Date.now());
58254
+ const owner = displaySafe(info.owner);
58255
+ const holder = owner ? `held by ${owner}${operation}` : `held by an unnamed holder`;
58256
+ held = `${heldClause ? `${heldClause} — ` : ""}${holder}, expires ${expires}`;
58257
+ if (owner) sawHolder = true;
58258
+ }
58259
+ } catch {}
58260
+ const advice = sawHolder ? `That process is still running — wait for it to finish. Only if you are certain it is gone` : `Wait for it to finish, or if you are certain no other process is active`;
58261
+ const recoveryCommand = buildForceUnlockCommand(stackName, region, recovery);
58262
+ const head = `Could not acquire lock for ${subject} '${displaySafe(stackName, { asciiOnly: true }) || "<unrenderable>"}' (${displaySafe(region, { asciiOnly: true }) || "<unrenderable>"}) — ${held}.` + (suffix ? ` ${suffix}` : "");
58263
+ if (!recoveryCommand) return `${head} ${advice}. No recovery command can be shown: the name or region recorded for this lock contains characters that cannot be reproduced safely on a command line, so any command shown here would address a different lock — inspect the lock object directly.`;
58264
+ return `${head} ${advice}, run: ${recoveryCommand}`;
58265
+ }
58266
+
58165
58267
  //#endregion
58166
58268
  //#region src/cli/commands/destroy-runner.ts
58167
58269
  /**
@@ -58298,9 +58400,64 @@ async function runDestroyForStack(stackName, state, ctx) {
58298
58400
  const regionForState = state.region ?? ctx.baseRegion;
58299
58401
  if (resourceCount === 0) {
58300
58402
  logger.info(`Stack ${stackName} has no resources, cleaning up state...`);
58301
- await ctx.stateBackend.deleteState(stackName, regionForState);
58302
- logger.info(`${green("✓")} State deleted`);
58403
+ let emptyLockHeld = false;
58404
+ let emptyInterrupted = false;
58405
+ const emptySigintHandler = () => {
58406
+ if (emptyInterrupted) {
58407
+ if (emptyLockHeld) {
58408
+ ctx.lockManager.releaseLock(stackName, regionForState).catch(() => {});
58409
+ process.stderr.write(`\nForce-quit: stack lock may not be released.${forceQuitRecoveryClause(stackName, regionForState, {
58410
+ profile: ctx.profile,
58411
+ stateBucket: ctx.stateBucket,
58412
+ statePrefix: ctx.statePrefix
58413
+ })}\n`);
58414
+ }
58415
+ process.exit(130);
58416
+ }
58417
+ emptyInterrupted = true;
58418
+ try {
58419
+ process.stderr.write(`\nInterrupt received - finishing the state cleanup for ${stackName} (press Ctrl-C again to force-quit)\n`);
58420
+ } catch {}
58421
+ };
58422
+ process.setMaxListeners(Math.max(process.getMaxListeners(), 100));
58423
+ process.on("SIGINT", emptySigintHandler);
58424
+ let emptyAcquired;
58425
+ try {
58426
+ emptyAcquired = await ctx.lockManager.acquireLock(stackName, regionForState, void 0, "destroy");
58427
+ } catch (acquireErr) {
58428
+ process.removeListener("SIGINT", emptySigintHandler);
58429
+ throw acquireErr;
58430
+ }
58431
+ if (!emptyAcquired) {
58432
+ process.removeListener("SIGINT", emptySigintHandler);
58433
+ throw new Error(await buildLockContentionMessage({
58434
+ lockManager: ctx.lockManager,
58435
+ stackName,
58436
+ region: regionForState,
58437
+ recovery: {
58438
+ profile: ctx.profile,
58439
+ stateBucket: ctx.stateBucket,
58440
+ statePrefix: ctx.statePrefix
58441
+ }
58442
+ }));
58443
+ }
58444
+ emptyLockHeld = true;
58445
+ try {
58446
+ const recheck = await ctx.stateBackend.getState(stackName, regionForState);
58447
+ if (!(!recheck || Object.keys(recheck.state.resources).length === 0)) throw new Error(`Stack '${stackName}' (${regionForState}) was empty when this run started but now has ${Object.keys(recheck.state.resources).length} resource(s) — another cdkd process deployed into it. Re-run the destroy to act on the current state.`);
58448
+ await ctx.stateBackend.deleteState(stackName, regionForState);
58449
+ logger.info(`${green("✓")} State deleted`);
58450
+ } finally {
58451
+ try {
58452
+ await ctx.lockManager.releaseLock(stackName, regionForState);
58453
+ } catch (releaseErr) {
58454
+ logger.warn(`Failed to release lock after empty-state cleanup: ${releaseErr instanceof Error ? releaseErr.message : String(releaseErr)}`);
58455
+ } finally {
58456
+ process.removeListener("SIGINT", emptySigintHandler);
58457
+ }
58458
+ }
58303
58459
  result.skippedEmpty = true;
58460
+ result.interrupted ||= emptyInterrupted;
58304
58461
  return result;
58305
58462
  }
58306
58463
  const needsStrongRefCheck = !!(state.outputs && Object.keys(state.outputs).length > 0);
@@ -58377,7 +58534,11 @@ async function runDestroyForStack(stackName, state, ctx) {
58377
58534
  if (draining) {
58378
58535
  if (lockHeld) {
58379
58536
  ctx.lockManager.releaseLock(stackName, regionForState).catch(() => {});
58380
- process.stderr.write(`\nForce-quit: stack lock may not be released. If the next run reports a lock, run: cdkd force-unlock ${stackName}\n`);
58537
+ process.stderr.write(`\nForce-quit: stack lock may not be released.${forceQuitRecoveryClause(stackName, regionForState, {
58538
+ profile: ctx.profile,
58539
+ stateBucket: ctx.stateBucket,
58540
+ statePrefix: ctx.statePrefix
58541
+ })}\n`);
58381
58542
  }
58382
58543
  process.exit(130);
58383
58544
  }
@@ -58390,7 +58551,16 @@ async function runDestroyForStack(stackName, state, ctx) {
58390
58551
  process.on("SIGINT", sigintHandler);
58391
58552
  try {
58392
58553
  logger.info(`\nAcquiring lock for stack ${stackName}...`);
58393
- if (!await ctx.lockManager.acquireLock(stackName, regionForState, void 0, "destroy")) throw new Error(`Could not acquire lock for stack '${stackName}' (${regionForState}) — another cdkd process holds it. Wait for it to finish, or run 'cdkd force-unlock ${stackName} --stack-region ${regionForState}' if you are certain no other process is active.`);
58554
+ if (!await ctx.lockManager.acquireLock(stackName, regionForState, void 0, "destroy")) throw new Error(await buildLockContentionMessage({
58555
+ lockManager: ctx.lockManager,
58556
+ stackName,
58557
+ region: regionForState,
58558
+ recovery: {
58559
+ profile: ctx.profile,
58560
+ stateBucket: ctx.stateBucket,
58561
+ statePrefix: ctx.statePrefix
58562
+ }
58563
+ }));
58394
58564
  } catch (error) {
58395
58565
  restoreBaseRegionAndClients();
58396
58566
  process.removeListener("SIGINT", sigintHandler);
@@ -58984,6 +59154,7 @@ var NestedStackProvider = class {
58984
59154
  skipConfirmation: true,
58985
59155
  ...ctx.exportIndexStore && { exportIndexStore: ctx.exportIndexStore },
58986
59156
  ...ctx.destroyOptions?.profile && { profile: ctx.destroyOptions.profile },
59157
+ ...ctx.destroyOptions?.statePrefix !== void 0 && { statePrefix: ctx.destroyOptions.statePrefix },
58987
59158
  ...deleteContext?.removeProtection === true && { removeProtection: true },
58988
59159
  ...(ctx.destroyOptions?.skipFinalSnapshot === true || ctx.options?.skipFinalSnapshot === true) && { skipFinalSnapshot: true },
58989
59160
  ...ctx.destroyOptions?.resourceWarnAfterMs !== void 0 && { resourceWarnAfterMs: ctx.destroyOptions.resourceWarnAfterMs },
@@ -59210,6 +59381,19 @@ function isCertificateInUseError(error) {
59210
59381
  * consumers (CloudFront, ALB) will fail to start if they reach the cert
59211
59382
  * before it issues, but that's the documented trade-off.
59212
59383
  *
59384
+ * **A create that fails after requesting DELETES its certificate** (issue
59385
+ * [#2169](https://github.com/go-to-k/cdkd/issues/2169)). `RequestCertificate`
59386
+ * materializes the certificate immediately and everything after it can fail —
59387
+ * most commonly the ISSUED wait, which a DNS-validated certificate whose
59388
+ * validation records are not live yet exhausts by construction. Before this,
59389
+ * the ARN was thrown away with the error: the certificate existed in AWS with
59390
+ * nothing naming it (absent from state, so invisible to `cdkd state show` and
59391
+ * unreachable by `cdkd destroy`), and the next `cdkd deploy` requested another
59392
+ * one — an orphan per attempt. `create()` now retires the certificate it just
59393
+ * requested before re-throwing, so a failed deploy leaves nothing behind and a
59394
+ * retry starts clean. See {@link ACMCertificateProvider.cleanupRequestedCertificate}
59395
+ * for why deleting is safe and why it is preferred over recording the remnant.
59396
+ *
59213
59397
  * **CloudFront cross-region note**: ACM certificates referenced by a
59214
59398
  * CloudFront Distribution MUST live in `us-east-1`. cdkd does not enforce
59215
59399
  * this — it's the developer's responsibility to deploy the certificate
@@ -59259,12 +59443,22 @@ var ACMCertificateProvider = class {
59259
59443
  if (Object.keys(options).length > 0) input["Options"] = options;
59260
59444
  const tags = properties["Tags"];
59261
59445
  if (tags && Array.isArray(tags) && tags.length > 0) input["Tags"] = tags;
59446
+ const idempotencyToken = acquireIdempotencyToken({
59447
+ scope: "RequestCertificate",
59448
+ logicalId,
59449
+ maxLength: 32,
59450
+ charset: "alphanumeric"
59451
+ });
59452
+ input["IdempotencyToken"] = idempotencyToken.value;
59453
+ let requestedArn;
59262
59454
  try {
59263
59455
  const certificateArn = (await this.acmClient.send(new RequestCertificateCommand(input))).CertificateArn;
59264
59456
  if (!certificateArn) throw new ProvisioningError(`RequestCertificate succeeded but no CertificateArn returned for ${logicalId}`, resourceType, logicalId);
59457
+ requestedArn = certificateArn;
59265
59458
  this.logger.debug(`Requested ACM certificate: ${certificateArn}`);
59266
- if (!(process.env["CDKD_NO_WAIT"] === "true")) await this.waitForCertificateIssued(certificateArn, logicalId);
59459
+ if (!(process.env["CDKD_NO_WAIT"] === "true")) await this.waitForCertificateIssued(certificateArn, logicalId, resourceType);
59267
59460
  else this.logger.warn(`Skipping wait for ACM certificate ${logicalId} (CDKD_NO_WAIT=true). Downstream consumers (CloudFront / ALB) will fail until the cert reaches ISSUED.`);
59461
+ idempotencyToken.release();
59268
59462
  return {
59269
59463
  physicalId: certificateArn,
59270
59464
  attributes: {
@@ -59273,9 +59467,84 @@ var ACMCertificateProvider = class {
59273
59467
  }
59274
59468
  };
59275
59469
  } catch (error) {
59276
- if (error instanceof CdkdError) throw error;
59470
+ const survivorNote = requestedArn ? await this.cleanupRequestedCertificate(requestedArn, logicalId, idempotencyToken) : void 0;
59471
+ if (error instanceof CdkdError) {
59472
+ if (survivorNote === void 0) throw error;
59473
+ throw new ProvisioningError(`${error.message} ${survivorNote}`, resourceType, logicalId, requestedArn, error);
59474
+ }
59277
59475
  const cause = error instanceof Error ? error : void 0;
59278
- throw new ProvisioningError(`Failed to create ACM certificate ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, void 0, cause);
59476
+ throw new ProvisioningError(`Failed to create ACM certificate ${logicalId}: ${error instanceof Error ? error.message : String(error)}` + (survivorNote === void 0 ? "" : ` ${survivorNote}`), resourceType, logicalId, requestedArn, cause);
59477
+ }
59478
+ }
59479
+ /**
59480
+ * Retire the certificate a FAILED `create()` just requested, so the failure
59481
+ * leaves nothing behind (issue
59482
+ * [#2169](https://github.com/go-to-k/cdkd/issues/2169)).
59483
+ *
59484
+ * Returns `undefined` when the certificate is gone, or a one-line note naming
59485
+ * the survivor when it could not be deleted. Never throws: the original
59486
+ * create error is what the user needs to see, and a cleanup failure must
59487
+ * degrade that message rather than replace it.
59488
+ *
59489
+ * ## Why DELETING is right, when the obvious objection is that it throws away
59490
+ * the user's DNS work
59491
+ *
59492
+ * It does not. ACM derives a domain's validation CNAME from the domain and
59493
+ * the account, not from the certificate, and documents that the record "must
59494
+ * be added to your DNS database only once": *"Without the need to repeat
59495
+ * validation, you can request additional ACM certificates for your fully
59496
+ * qualified domain name (FQDN) for as long as the CNAME record remains in
59497
+ * place ... You can also replace a deleted certificate."*
59498
+ * (https://docs.aws.amazon.com/acm/latest/userguide/dns-validation.html)
59499
+ * So the CNAMEs a user adds after seeing this failure validate the NEXT
59500
+ * certificate too, and the retry issues normally. The intuition that a
59501
+ * `PENDING_VALIDATION` certificate is precious does not hold for ACM.
59502
+ *
59503
+ * ## Why not RECORD the remnant in cdkd state instead
59504
+ *
59505
+ * That was this fix's first shape, and it does not work. A state record makes
59506
+ * the next deploy diff the resource as `NO_CHANGE` -- the recorded properties
59507
+ * ARE the template's -- so cdkd would never touch that certificate again:
59508
+ * `cdkd deploy` would print "No changes detected" and exit 0 over a
59509
+ * certificate that is still unusable, and the CloudFront / ALB consuming it
59510
+ * would fail with no explanation. Turning a loud failure into a silent one is
59511
+ * worse than the orphan it fixes. Making the diff re-provision it instead
59512
+ * needs a marker on the state record -- a schema bump -- to buy behaviour
59513
+ * that deleting gives for free.
59514
+ *
59515
+ * ## The "keep it" escape hatch already exists
59516
+ *
59517
+ * A user who WANTS the certificate to outlive the deploy has
59518
+ * `CDKD_NO_WAIT=true` (`cdkd deploy --no-wait`), which returns success with
59519
+ * the certificate recorded in state and never reaches this path. "Wait for
59520
+ * it" and "keep it even if the wait fails" are different requests, and cdkd
59521
+ * already has a flag for the second.
59522
+ */
59523
+ /**
59524
+ * The region an ACM ARN names, for the manual retire command. Taken from the
59525
+ * ARN rather than from the client so the command a user pastes matches the
59526
+ * certificate it names even when their shell default differs. Falls back to
59527
+ * the literal placeholder for a shape this cannot parse -- a wrong region
59528
+ * would make the command silently address a different account/region.
59529
+ */
59530
+ regionOfArn(arn) {
59531
+ return arn.split(":")[3] || "<region>";
59532
+ }
59533
+ async cleanupRequestedCertificate(certificateArn, logicalId, idempotencyToken) {
59534
+ try {
59535
+ await this.acmClient.send(new DeleteCertificateCommand({ CertificateArn: certificateArn }));
59536
+ this.logger.info(`Deleted the ACM certificate ${certificateArn} that the failed create of ${logicalId} requested, so nothing is left behind. DNS validation records you have already added stay valid for the retry.`);
59537
+ idempotencyToken.release();
59538
+ return;
59539
+ } catch (cleanupError) {
59540
+ if (cleanupError instanceof ResourceNotFoundException$7 || cleanupError instanceof Error && cleanupError.name === "ResourceNotFoundException") {
59541
+ this.logger.info(`The ACM certificate ${certificateArn} that the failed create of ${logicalId} requested was already gone; nothing to clean up.`);
59542
+ idempotencyToken.release();
59543
+ return;
59544
+ }
59545
+ const detail = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
59546
+ this.logger.warn(`Could not delete the ACM certificate ${certificateArn} that the failed create of ${logicalId} requested: ${detail}`);
59547
+ return `The certificate ${certificateArn} this attempt created could NOT be deleted, and cdkd is not tracking it -- retire it with \`aws acm delete-certificate --certificate-arn ${certificateArn} --region ${this.regionOfArn(certificateArn)}\` (the reason is in the warning above).`;
59279
59548
  }
59280
59549
  }
59281
59550
  async update(logicalId, physicalId, resourceType, properties, previousProperties) {
@@ -59448,11 +59717,25 @@ var ACMCertificateProvider = class {
59448
59717
  * Throws on `VALIDATION_TIMED_OUT` / `FAILED` (terminal failures) and on
59449
59718
  * polling-cap exhaustion (treated as timeout). SIGINT short-circuits the
59450
59719
  * loop and returns control to the deploy engine's cleanup path.
59720
+ *
59721
+ * Both throws are `ProvisioningError`s carrying `certificateArn` as their
59722
+ * `physicalId` (issue
59723
+ * [#2169](https://github.com/go-to-k/cdkd/issues/2169)). They used to be
59724
+ * plain `Error`s whose only reference to the certificate was the message
59725
+ * TEXT, which is not something a caller can act on. The certificate is real
59726
+ * by the time either fires, so the error that reports the failure is the one
59727
+ * piece of the failure path that should still be able to name it.
59728
+ *
59729
+ * Says nothing about what happens to the certificate afterwards: the caller
59730
+ * owns that. `create()`'s catch retires it (see
59731
+ * {@link ACMCertificateProvider.cleanupRequestedCertificate}) and appends a
59732
+ * note to this message only when that cleanup could NOT.
59451
59733
  */
59452
- async waitForCertificateIssued(certificateArn, logicalId) {
59734
+ async waitForCertificateIssued(certificateArn, logicalId, resourceType) {
59453
59735
  this.logger.debug(`Waiting for ACM certificate ${certificateArn} to reach ISSUED status...`);
59454
59736
  let interrupted = false;
59455
- let validationOptionsLogged = false;
59737
+ /** The last validation-record block printed, so we re-print only on a CHANGE. */
59738
+ let lastValidationOptions;
59456
59739
  const sigintHandler = () => {
59457
59740
  interrupted = true;
59458
59741
  };
@@ -59470,17 +59753,20 @@ var ACMCertificateProvider = class {
59470
59753
  this.logger.debug(`ACM certificate ${certificateArn} is ISSUED`);
59471
59754
  return;
59472
59755
  }
59473
- if (status === "FAILED" || status === "VALIDATION_TIMED_OUT" || status === "INACTIVE" || status === "REVOKED" || status === "EXPIRED") throw new Error(`ACM certificate ${logicalId} (${certificateArn}) entered terminal status ${status} during validation. Check ACM console / DNS records to diagnose.`);
59474
- if (status === "PENDING_VALIDATION" && !validationOptionsLogged && validations.length > 0) {
59475
- this.logValidationOptions(validations);
59476
- validationOptionsLogged = true;
59756
+ if (status === "FAILED" || status === "VALIDATION_TIMED_OUT" || status === "INACTIVE" || status === "REVOKED" || status === "EXPIRED") throw new ProvisioningError(`ACM certificate ${logicalId} (${certificateArn}) entered terminal status ${status} during validation. Check ACM console / DNS records to diagnose.`, resourceType, logicalId, certificateArn);
59757
+ if (status === "PENDING_VALIDATION") {
59758
+ const rendered = this.renderValidationOptions(validations);
59759
+ if (rendered !== void 0 && rendered !== lastValidationOptions) {
59760
+ this.logger.info(rendered);
59761
+ lastValidationOptions = rendered;
59762
+ }
59477
59763
  }
59478
59764
  this.logger.debug(`ACM certificate ${certificateArn} status: ${status} (attempt ${attempt}/${this.maxPollAttempts})`);
59479
59765
  const sleepEnd = Date.now() + this.pollIntervalMs;
59480
59766
  const tickMs = Math.min(1e3, this.pollIntervalMs);
59481
59767
  while (Date.now() < sleepEnd && !interrupted) await new Promise((resolve) => setTimeout(resolve, tickMs));
59482
59768
  }
59483
- throw new Error(`ACM certificate ${logicalId} (${certificateArn}) did not reach ISSUED status within ${this.maxPollAttempts * this.pollIntervalMs / 1e3}s. If your DNS zone is manually managed, you may need to increase --resource-timeout AWS::CertificateManager::Certificate=<duration> or set CDKD_NO_WAIT=true.`);
59769
+ throw new ProvisioningError(`ACM certificate ${logicalId} (${certificateArn}) did not reach ISSUED status within ${this.maxPollAttempts * this.pollIntervalMs / 1e3}s. ` + (lastValidationOptions === void 0 ? "AWS had not published this certificate's validation records before the wait ran out, so none were printed — re-run the deploy to request them again. " : `Add the validation records printed above to your DNS zone and re-run the deploy. `) + "ACM reuses a domain's validation CNAME across certificates, so records you add now validate the next attempt. To wait LONGER, raise CDKD_ACM_POLL_ATTEMPTS / CDKD_ACM_POLL_INTERVAL_MS — this cap is the provider's OWN and is what just fired; --resource-timeout alone will not lengthen it. Past ~30m also raise --resource-timeout AWS::CertificateManager::Certificate=<duration>, or the engine's per-resource deadline becomes the shorter of the two. Or set CDKD_NO_WAIT=true to keep the certificate and validate it out of band.", resourceType, logicalId, certificateArn);
59484
59770
  } finally {
59485
59771
  process.removeListener("SIGINT", sigintHandler);
59486
59772
  }
@@ -59490,16 +59776,13 @@ var ACMCertificateProvider = class {
59490
59776
  * user can copy / paste them into Route 53 / Cloudflare / etc. while the
59491
59777
  * cert is still PENDING_VALIDATION.
59492
59778
  */
59493
- logValidationOptions(validations) {
59779
+ renderValidationOptions(validations) {
59494
59780
  const lines = ["ACM certificate is PENDING_VALIDATION. Add the following DNS records to validate:"];
59495
59781
  for (const v of validations) if (v.ValidationMethod === "DNS" && v.ResourceRecord) {
59496
59782
  const r = v.ResourceRecord;
59497
59783
  lines.push(` ${v.DomainName} — ${r.Type} ${r.Name} -> ${r.Value}`);
59498
- } else if (v.ValidationMethod === "EMAIL") {
59499
- const emails = (v.ValidationEmails ?? []).join(", ");
59500
- lines.push(` ${v.DomainName} — confirmation email sent to: ${emails || "<none>"}`);
59501
- }
59502
- this.logger.info(lines.join("\n"));
59784
+ } else if (v.ValidationMethod === "EMAIL" && (v.ValidationEmails ?? []).length > 0) lines.push(` ${v.DomainName} — confirmation email sent to: ${v.ValidationEmails.join(", ")}`);
59785
+ return lines.length === 1 ? void 0 : lines.join("\n");
59503
59786
  }
59504
59787
  async updateTags(certificateArn, newTags, oldTags) {
59505
59788
  const newTagMap = new Map((newTags || []).map((t) => [t.Key, t.Value]));
@@ -59905,7 +60188,7 @@ async function deployCommand(stacks, options) {
59905
60188
  let deployInterrupted = false;
59906
60189
  const topLevelSigintHandler = () => {
59907
60190
  if (deployInterrupted) {
59908
- process.stderr.write("\nForce-quit: stack locks may not be released. If the next run reports a lock, run: cdkd force-unlock <stackName>\n");
60191
+ process.stderr.write("\nForce-quit: stack locks may not be released. If the next run reports a lock, run this for EACH stack it names (the region-qualified form — see the message that run prints): cdkd force-unlock <stackName> --stack-region <region>\n");
59909
60192
  process.exit(130);
59910
60193
  }
59911
60194
  process.stderr.write("\nInterrupted — waiting for in-progress operations to complete...\n");
@@ -60172,6 +60455,10 @@ async function deployCommand(stacks, options) {
60172
60455
  dagBuilder,
60173
60456
  diffCalculator,
60174
60457
  options: deployEngineOptions,
60458
+ destroyOptions: {
60459
+ statePrefix: options.statePrefix,
60460
+ ...options.profile && { profile: options.profile }
60461
+ },
60175
60462
  ...assetRedirect && { assetRedirect }
60176
60463
  }, () => stackDeployEngine.deploy(stackInfo.stackName, stackInfo.template));
60177
60464
  logger.info(`\n${bold("Deployment Summary:")}`);
@@ -66142,6 +66429,11 @@ function setAtPath(target, path, value) {
66142
66429
  */
66143
66430
  async function runAccept(reports, stateBackend, stateConfig, awsClients, options) {
66144
66431
  const logger = getLogger();
66432
+ const lockRecovery = {
66433
+ profile: options.profile,
66434
+ stateBucket: stateConfig.bucket,
66435
+ statePrefix: stateConfig.prefix
66436
+ };
66145
66437
  printAcceptPlan(reports);
66146
66438
  if (options.dryRun) {
66147
66439
  logger.info("--dry-run: state will NOT be written. Re-run without --dry-run to apply.");
@@ -66164,7 +66456,12 @@ async function runAccept(reports, stateBackend, stateConfig, awsClients, options
66164
66456
  skipped: () => []
66165
66457
  }));
66166
66458
  if (driftedOutcomes.length === 0) continue;
66167
- if (!await lockManager.acquireLock(report.stackName, report.region, owner, "drift-accept")) throw new Error(`Could not acquire lock for stack '${report.stackName}' (${report.region}) — another cdkd process holds it. Wait for it to finish, or run 'cdkd force-unlock ${report.stackName} --stack-region ${report.region}' if you are certain no other process is active.`);
66459
+ if (!await lockManager.acquireLock(report.stackName, report.region, owner, "drift-accept")) throw new Error(await buildLockContentionMessage({
66460
+ lockManager,
66461
+ stackName: report.stackName,
66462
+ region: report.region,
66463
+ recovery: lockRecovery
66464
+ }));
66168
66465
  try {
66169
66466
  const resources = { ...report.state.resources };
66170
66467
  for (const outcome of driftedOutcomes) {
@@ -66761,6 +67058,11 @@ function deepEqualUnordered(a, b) {
66761
67058
  */
66762
67059
  async function runRevert(reports, providerRegistry, stateBackend, stateConfig, awsClients, options) {
66763
67060
  const logger = getLogger();
67061
+ const lockRecovery = {
67062
+ profile: options.profile,
67063
+ stateBucket: stateConfig.bucket,
67064
+ statePrefix: stateConfig.prefix
67065
+ };
66764
67066
  printRevertPlan(reports);
66765
67067
  if (options.dryRun) {
66766
67068
  logger.info("--dry-run: AWS will NOT be modified. Re-run without --dry-run to apply.");
@@ -66788,11 +67090,16 @@ async function runRevert(reports, providerRegistry, stateBackend, stateConfig, a
66788
67090
  skipped: () => []
66789
67091
  }));
66790
67092
  if (driftedOutcomes.length === 0) continue;
66791
- if (!await lockManager.acquireLock(report.stackName, report.region, owner, "drift-revert")) throw new Error(`Could not acquire lock for stack '${report.stackName}' (${report.region}) — another cdkd process holds it. Wait for it to finish, or run 'cdkd force-unlock ${report.stackName} --stack-region ${report.region}' if you are certain no other process is active.`);
67093
+ if (!await lockManager.acquireLock(report.stackName, report.region, owner, "drift-revert")) throw new Error(await buildLockContentionMessage({
67094
+ lockManager,
67095
+ stackName: report.stackName,
67096
+ region: report.region,
67097
+ recovery: lockRecovery
67098
+ }));
66792
67099
  const narrowedByLogicalId = /* @__PURE__ */ new Map();
66793
- const revertSecretResolvers = new DriftSecretResolvers(report.region);
66794
- const revertProducerRegions = producerRegionsFromState(report.state);
66795
67100
  try {
67101
+ const revertSecretResolvers = new DriftSecretResolvers(report.region);
67102
+ const revertProducerRegions = producerRegionsFromState(report.state);
66796
67103
  await runWithConcurrency(driftedOutcomes.map((outcome) => async () => {
66797
67104
  const stateResource = report.state.resources[outcome.logicalId];
66798
67105
  if (!stateResource) {
@@ -67415,6 +67722,7 @@ async function destroyCommand(stackArgs, options) {
67415
67722
  exportIndexStore,
67416
67723
  destroyOptions: {
67417
67724
  ...options.profile && { profile: options.profile },
67725
+ statePrefix: options.statePrefix,
67418
67726
  ...options.removeProtection === true && { removeProtection: true },
67419
67727
  ...options.skipFinalSnapshot === true && { skipFinalSnapshot: true },
67420
67728
  ...options.resourceWarnAfter?.globalMs !== void 0 && { resourceWarnAfterMs: options.resourceWarnAfter.globalMs },
@@ -67430,6 +67738,7 @@ async function destroyCommand(stackArgs, options) {
67430
67738
  baseRegion: region,
67431
67739
  ...options.profile && { profile: options.profile },
67432
67740
  stateBucket,
67741
+ statePrefix: options.statePrefix,
67433
67742
  skipConfirmation: options.yes || options.force,
67434
67743
  removeProtection: options.removeProtection === true,
67435
67744
  skipFinalSnapshot: options.skipFinalSnapshot === true,
@@ -69003,6 +69312,11 @@ async function exportCommand(stackArg, options) {
69003
69312
  bucket: stateBucket,
69004
69313
  prefix: options.statePrefix
69005
69314
  };
69315
+ const lockRecovery = {
69316
+ profile: options.profile,
69317
+ stateBucket,
69318
+ statePrefix: options.statePrefix
69319
+ };
69006
69320
  const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
69007
69321
  ...options.region && { region: options.region },
69008
69322
  ...options.profile && { profile: options.profile }
@@ -69078,7 +69392,12 @@ async function exportCommand(stackArg, options) {
69078
69392
  }
69079
69393
  const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
69080
69394
  if (!options.dryRun) {
69081
- if (!await lockManager.acquireLock(resolvedStackName, targetRegion, owner, "export")) throw new Error(`Could not acquire lock for stack '${resolvedStackName}' (${targetRegion}) — another cdkd process holds it. Wait for it to finish, or run 'cdkd force-unlock ${resolvedStackName} --stack-region ${targetRegion}' if you are certain no other process is active.`);
69395
+ if (!await lockManager.acquireLock(resolvedStackName, targetRegion, owner, "export")) throw new Error(await buildLockContentionMessage({
69396
+ lockManager,
69397
+ stackName: resolvedStackName,
69398
+ region: targetRegion,
69399
+ recovery: lockRecovery
69400
+ }));
69082
69401
  }
69083
69402
  try {
69084
69403
  if (nestedStackRows.length > 0) {
@@ -69098,6 +69417,7 @@ async function exportCommand(stackArg, options) {
69098
69417
  if (missingNested.length > 0) throw new Error(`Template requires parameter(s) without defaults: ${missingNested.join(", ")}. Pass each one as --parameter Key=Value (or set a Default in the CDK code).`);
69099
69418
  const childOverrides = parseCfnChildStackNameOverrides(options.cfnChildStackName);
69100
69419
  if ((await runPerStackImportLoop({
69420
+ lockRecovery,
69101
69421
  rootStackName: resolvedStackName,
69102
69422
  rootRegion: targetRegion,
69103
69423
  rootStackInfoNestedTemplates: rootNestedTemplatePaths,
@@ -70994,7 +71314,15 @@ async function runPerStackImportLoop(args) {
70994
71314
  try {
70995
71315
  await deps.lockManager.acquireLockWithRetry(node.stackName, node.region, deps.lockOwner, "export", CHILD_LOCK_RETRY_MAX_ATTEMPTS, CHILD_LOCK_RETRY_DELAY_MS);
70996
71316
  } catch (err) {
70997
- throw new Error(`Could not acquire lock for nested-stack child '${node.stackName}' (${node.region}) — another cdkd process held it through the retry window. Wait for it to finish, or run 'cdkd force-unlock ${node.stackName} --stack-region ${node.region}' if you are certain no other process is active. No CloudFormation changeset has been submitted; cdkd state is unchanged.`, { cause: err instanceof Error ? err : void 0 });
71317
+ throw new Error(await buildLockContentionMessage({
71318
+ lockManager: deps.lockManager,
71319
+ stackName: node.stackName,
71320
+ region: node.region,
71321
+ subject: "nested-stack child",
71322
+ heldClause: "another cdkd process held it through the retry window",
71323
+ recovery: args.lockRecovery,
71324
+ suffix: "No CloudFormation changeset has been submitted; cdkd state is unchanged."
71325
+ }), { cause: err instanceof Error ? err : void 0 });
70998
71326
  }
70999
71327
  acquiredChildLocks.push({
71000
71328
  stackName: node.stackName,
@@ -71930,8 +72258,12 @@ async function stateOrphanCommand(stackArgs, options) {
71930
72258
  }
71931
72259
  if (!options.force) {
71932
72260
  for (const target of targets) if (await setup.lockManager.isLocked(stackName, target.region)) {
71933
- const where = target.region ?? "(legacy)";
71934
- throw new Error(`Stack '${stackName}' (${where}) is locked. Run 'cdkd force-unlock ${stackName}${target.region ? ` --stack-region ${target.region}` : ""}' first, or pass --force to remove anyway.`);
72261
+ const where = target.region ? displaySafe(target.region, { asciiOnly: true }) || "<unrenderable>" : "(legacy)";
72262
+ throw new Error(`Stack '${displaySafe(stackName, { asciiOnly: true })}' (${where}) is locked. Run: ${buildForceUnlockCommand(stackName, target.region, {
72263
+ profile: options.profile,
72264
+ stateBucket: setup.bucket,
72265
+ statePrefix: options.statePrefix
72266
+ }) || "a command cdkd cannot show safely: the recorded name or region would not survive a command line intact, so inspect the lock object directly"} first, or pass --force to remove anyway.`);
71935
72267
  }
71936
72268
  }
71937
72269
  if (!options.yes && !options.force) {
@@ -71951,9 +72283,13 @@ async function stateOrphanCommand(stackArgs, options) {
71951
72283
  }
71952
72284
  for (const target of targets) {
71953
72285
  if (target.region) {
72286
+ await warnOnLiveForeignLock(setup.lockManager, stackName, target.region, logger);
71954
72287
  await setup.stateBackend.deleteState(stackName, target.region);
71955
72288
  await setup.lockManager.forceReleaseLock(stackName, target.region);
71956
- } else await setup.lockManager.forceReleaseLock(stackName, void 0);
72289
+ } else {
72290
+ await warnOnLiveForeignLock(setup.lockManager, stackName, void 0, logger);
72291
+ await setup.lockManager.forceReleaseLock(stackName, void 0);
72292
+ }
71957
72293
  logger.info(`✓ Removed state for stack: ${formatStackRef(target)}`);
71958
72294
  }
71959
72295
  }
@@ -72085,6 +72421,7 @@ async function stateDestroyCommand(stackArgs, options) {
72085
72421
  exportIndexStore: setup.exportIndexStore,
72086
72422
  destroyOptions: {
72087
72423
  ...options.profile && { profile: options.profile },
72424
+ statePrefix: options.statePrefix,
72088
72425
  ...options.removeProtection === true && { removeProtection: true },
72089
72426
  ...options.skipFinalSnapshot === true && { skipFinalSnapshot: true },
72090
72427
  ...options.resourceWarnAfter?.globalMs !== void 0 && { resourceWarnAfterMs: options.resourceWarnAfter.globalMs },
@@ -72100,6 +72437,7 @@ async function stateDestroyCommand(stackArgs, options) {
72100
72437
  baseRegion: setup.region,
72101
72438
  ...options.profile && { profile: options.profile },
72102
72439
  stateBucket: setup.bucket,
72440
+ statePrefix: options.statePrefix,
72103
72441
  skipConfirmation: options.yes || options.all === true,
72104
72442
  removeProtection: options.removeProtection === true,
72105
72443
  skipFinalSnapshot: options.skipFinalSnapshot === true,
@@ -72461,7 +72799,12 @@ async function stateRefreshObservedCommand(stackArgs, options) {
72461
72799
  if (!target.region) throw new Error(`Stack '${target.stackName}' has only a legacy state record without a region. Run 'cdkd deploy ${target.stackName}' (or any cdkd write) first to migrate it to the region-scoped layout, then re-run refresh-observed.`);
72462
72800
  const counts = await refreshObservedForStack(target.stackName, target.region, setup.stateBackend, setup.lockManager, providerRegistry, {
72463
72801
  dryRun: options.dryRun ?? false,
72464
- logger
72802
+ logger,
72803
+ lockRecovery: {
72804
+ profile: options.profile,
72805
+ stateBucket: setup.bucket,
72806
+ statePrefix: options.statePrefix
72807
+ }
72465
72808
  });
72466
72809
  totalRefreshed += counts.refreshed;
72467
72810
  totalUnsupported += counts.unsupported;
@@ -72488,6 +72831,34 @@ async function stateRefreshObservedCommand(stackArgs, options) {
72488
72831
  */
72489
72832
  const NO_RECORDED_SECRETS = /* @__PURE__ */ new Map();
72490
72833
  /**
72834
+ * Warn before `cdkd state orphan` force-releases a lock that is still LIVE.
72835
+ *
72836
+ * `forceReleaseLock` is unconditional by design — a stuck lock must never make
72837
+ * a state record unremovable — but it deletes an in-flight `cdkd deploy`'s lock
72838
+ * just as readily as a stale one, and it did so silently (issue #2171). The
72839
+ * write it enables has already happened by the time anyone notices, so the
72840
+ * only useful thing is to name the owner at the moment of the release.
72841
+ *
72842
+ * Best-effort in both directions: an expired lock is not worth a line, and a
72843
+ * failed read must not block the removal the user asked for.
72844
+ *
72845
+ * (Issue #2171's own text called this command `cdkd state rm`; no such command
72846
+ * exists -- this file registers it as `orphan`.)
72847
+ */
72848
+ async function warnOnLiveForeignLock(lockManager, stackName, region, logger) {
72849
+ try {
72850
+ const info = await lockManager.getLockInfo(stackName, region);
72851
+ if (!info) return;
72852
+ const expiryKnown = Number.isFinite(info.expiresAt);
72853
+ if (expiryKnown && info.expiresAt <= Date.now()) return;
72854
+ const safeStack = displaySafe(stackName, { asciiOnly: true }) || "<unrenderable>";
72855
+ const where = region ? `${safeStack} (${displaySafe(region, { asciiOnly: true }) || "<unrenderable>"})` : `${safeStack} (legacy lock key)`;
72856
+ const owner = info.owner;
72857
+ const operation = info.operation ? `, operation: ${info.operation}` : "";
72858
+ logger.warn(`Force-releasing a LIVE lock on ${where} ${owner ? `held by ${owner}${operation}` : "held by an unnamed holder"}. ` + (owner && expiryKnown ? `That process is still running and will keep writing; its next state write ` : expiryKnown ? `The lock has not expired, so a process may still be writing; its next state write ` : `The lock records no readable expiry, so a process may still be writing; its next state write `) + `may recreate the record being removed here.`);
72859
+ } catch {}
72860
+ }
72861
+ /**
72491
72862
  * Refresh the `observedProperties` of every resource in one stack
72492
72863
  * record. Returns counts so the caller can aggregate across `--all`.
72493
72864
  *
@@ -72497,7 +72868,7 @@ const NO_RECORDED_SECRETS = /* @__PURE__ */ new Map();
72497
72868
  * for the underlying SDK reads.
72498
72869
  */
72499
72870
  async function refreshObservedForStack(stackName, region, stateBackend, lockManager, providerRegistry, opts) {
72500
- const { logger } = opts;
72871
+ const { logger, lockRecovery } = opts;
72501
72872
  const result = await stateBackend.getState(stackName, region);
72502
72873
  if (!result) throw new Error(`No state found for stack '${stackName}' (${region}). Run 'cdkd state list' to see available stacks.`);
72503
72874
  const { state, etag, migrationPending } = result;
@@ -72535,7 +72906,12 @@ async function refreshObservedForStack(stackName, region, stateBackend, lockMana
72535
72906
  };
72536
72907
  }
72537
72908
  const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
72538
- if (!await lockManager.acquireLock(stackName, region, owner, "state-refresh-observed")) throw new Error(`Could not acquire lock for stack '${stackName}' (${region}) — another cdkd process holds it. Wait for it to finish, or run 'cdkd force-unlock ${stackName} --stack-region ${region}' if you are certain no other process is active.`);
72909
+ if (!await lockManager.acquireLock(stackName, region, owner, "state-refresh-observed")) throw new Error(await buildLockContentionMessage({
72910
+ lockManager,
72911
+ stackName,
72912
+ region,
72913
+ recovery: lockRecovery
72914
+ }));
72539
72915
  try {
72540
72916
  let refreshed = 0;
72541
72917
  let unsupported = 0;
@@ -72878,7 +73254,10 @@ async function rollbackCommand(stackArg, options) {
72878
73254
  awsClients: setup.awsClients,
72879
73255
  stateBucket: setup.bucket,
72880
73256
  exportIndexStore: setup.exportIndexStore,
72881
- destroyOptions: { ...options.profile && { profile: options.profile } }
73257
+ destroyOptions: {
73258
+ ...options.profile && { profile: options.profile },
73259
+ statePrefix: options.statePrefix
73260
+ }
72882
73261
  }, () => withStackName(stackName, async () => {
72883
73262
  let failedOpFailures = 0;
72884
73263
  let failedOpWarnings = 0;
@@ -74915,7 +75294,16 @@ async function orphanCommand(pathArgs, options) {
74915
75294
  logger.info(`Target: ${stackInfo.stackName} (${targetRegion}); orphaning ${orphanLogicalIds.length} resource(s): ${orphanLogicalIds.join(", ")}`);
74916
75295
  const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
74917
75296
  if (!options.dryRun) {
74918
- if (!await lockManager.acquireLock(stackInfo.stackName, targetRegion, owner, "orphan")) throw new Error(`Could not acquire lock for stack '${stackInfo.stackName}' (${targetRegion}) — another cdkd process holds it. Wait for it to finish, or run 'cdkd force-unlock ${stackInfo.stackName} --stack-region ${targetRegion}' if you are certain no other process is active.`);
75297
+ if (!await lockManager.acquireLock(stackInfo.stackName, targetRegion, owner, "orphan")) throw new Error(await buildLockContentionMessage({
75298
+ lockManager,
75299
+ stackName: stackInfo.stackName,
75300
+ region: targetRegion,
75301
+ recovery: {
75302
+ profile: options.profile,
75303
+ stateBucket,
75304
+ statePrefix: options.statePrefix
75305
+ }
75306
+ }));
74919
75307
  }
74920
75308
  try {
74921
75309
  const stateData = await stateBackend.getState(stackInfo.stackName, targetRegion);
@@ -75401,6 +75789,11 @@ async function importCommand(stackArg, options) {
75401
75789
  bucket: stateBucket,
75402
75790
  prefix: options.statePrefix
75403
75791
  };
75792
+ const lockRecovery = {
75793
+ profile: options.profile,
75794
+ stateBucket,
75795
+ statePrefix: options.statePrefix
75796
+ };
75404
75797
  const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
75405
75798
  ...options.region && { region: options.region },
75406
75799
  ...options.profile && { profile: options.profile }
@@ -75501,7 +75894,16 @@ async function importCommand(stackArg, options) {
75501
75894
  }
75502
75895
  for (const overrideId of overrides.keys()) if (!templateLogicalIds.has(overrideId)) throw new Error(`--resource / --resource-mapping references logical ID '${overrideId}' which is not in the synthesized template for stack '${stackInfo.stackName}'. Available IDs: ${[...templateLogicalIds].join(", ")}`);
75503
75896
  const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
75504
- if (!await lockManager.acquireLock(stackInfo.stackName, targetRegion, owner, "import")) throw new Error(`Could not acquire lock for stack '${stackInfo.stackName}' (${targetRegion}) — another cdkd process holds it. Wait for it to finish, or run 'cdkd force-unlock ${stackInfo.stackName} --stack-region ${targetRegion}' if you are certain no other process is active.`);
75897
+ if (!await lockManager.acquireLock(stackInfo.stackName, targetRegion, owner, "import")) throw new Error(await buildLockContentionMessage({
75898
+ lockManager,
75899
+ stackName: stackInfo.stackName,
75900
+ region: targetRegion,
75901
+ recovery: {
75902
+ profile: options.profile,
75903
+ stateBucket,
75904
+ statePrefix: options.statePrefix
75905
+ }
75906
+ }));
75505
75907
  try {
75506
75908
  const rows = [];
75507
75909
  for (const { logicalId, resource } of resources) {
@@ -75565,6 +75967,7 @@ async function importCommand(stackArg, options) {
75565
75967
  logger.info(`✓ State written: ${stackInfo.stackName} (${targetRegion})`);
75566
75968
  logger.info(` ${importedRows.length} resource(s) imported. Run 'cdkd diff' to see how the imported state lines up with the template.`);
75567
75969
  if (migrationCfnStackName && migrationTree && accountIdForNestedSynth) await importNestedStackChildrenRecursive({
75970
+ lockRecovery,
75568
75971
  parentStackName: stackInfo.stackName,
75569
75972
  parentRegion: targetRegion,
75570
75973
  parentNestedTemplates: stackInfo.nestedTemplates ?? {},
@@ -76230,7 +76633,13 @@ async function importNestedStackChildrenRecursive(args) {
76230
76633
  const childRewritten = rewriteTemplateAssetReferences(childTemplate, assetRedirect);
76231
76634
  if (childRewritten > 0) logger.info(`Note: ${childRewritten} asset reference(s) in nested stack ${childStackName} are recorded in state at their pre-rewrite (CDK bootstrap) values, so the next 'cdkd deploy' repoints any resource that still holds them to cdkd asset storage.`);
76232
76635
  }
76233
- if (!await lockManager.acquireLock(childStackName, childRegion, lockOwner, "import")) throw new Error(`Could not acquire lock for nested stack '${childStackName}' (${childRegion}) — another cdkd process holds it. Wait for it to finish, or run 'cdkd force-unlock ${childStackName} --stack-region ${childRegion}' if you are certain no other process is active.`);
76636
+ if (!await lockManager.acquireLock(childStackName, childRegion, lockOwner, "import")) throw new Error(await buildLockContentionMessage({
76637
+ lockManager,
76638
+ stackName: childStackName,
76639
+ region: childRegion,
76640
+ subject: "nested stack",
76641
+ recovery: args.lockRecovery
76642
+ }));
76234
76643
  try {
76235
76644
  const childResources = collectImportableResources(childTemplate);
76236
76645
  const childTemplateLogicalIds = new Set(childResources.map((r) => r.logicalId));
@@ -76272,6 +76681,7 @@ async function importNestedStackChildrenRecursive(args) {
76272
76681
  await stateBackend.saveState(childStackName, childRegion, childStackState);
76273
76682
  logger.info(`✓ Nested stack state written: ${childStackName} (${childRegion}) — ${rows.filter((r) => r.outcome === "imported").length} resource(s) imported.`);
76274
76683
  if (childTreeNode.nested.size > 0) await importNestedStackChildrenRecursive({
76684
+ lockRecovery: args.lockRecovery,
76275
76685
  parentStackName: childStackName,
76276
76686
  parentRegion: childRegion,
76277
76687
  parentNestedTemplates: indexGrandchildTemplatePaths(childTemplate, childTemplatePath),
@@ -86145,7 +86555,7 @@ function createMigrateCommand() {
86145
86555
  */
86146
86556
  function buildProgram() {
86147
86557
  const program = new Command();
86148
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.38");
86558
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.40");
86149
86559
  program.hook("preAction", (_thisCommand, actionCommand) => {
86150
86560
  const { profile } = actionCommand.optsWithGlobals();
86151
86561
  if (profile !== void 0) process.env["AWS_PROFILE"] = profile;